The data exists. Now it must become understandable.
Routes can recognize requests and controllers can prepare responses. But a PHP array containing tasks is not yet a user experience. Information must be organized, visual hierarchy created, correct actions offered, and errors explained clearly.
That is the role of the View layer. It sits between internal data and what a person actually sees. A good view does more than produce valid HTML: it protects output, anticipates empty collections, preserves invalid input, and makes the journey obvious on desktop and phone.
By the end, the application will have a task list, detail page, create and edit forms, shared layout, reusable partials, accessible errors, and confirmations after redirects.
Understand the role of a view
A view turns controller-prepared data into a document for the user. In a classic Web application, that document is usually HTML with links, forms, and messages.
The view does not decide which tasks belong to the user and does not create resources. Those decisions were made before rendering. It receives a data contract and focuses on presentation.
This separation lets design change without rewriting business rules and lets controllers be tested without parsing the full layout.
A view presents an existing decision; it replaces neither controller nor model.
Pass data explicitly
The second argument of view() is the contract between controller and template. Each key becomes a render variable: tasks, title, currentFilter, or permissions.
Choose names that describe content rather than appearance. tasks remains useful if a list becomes a grid; leftColumnTasks couples data to one design.
Avoid magical globally available variables. An explicit contract reveals dependencies and produces an understandable error when data is missing.
A view should reveal its dependencies through controller-supplied data.
return view('tasks/index', [
'title' => 'My tasks',
'tasks' => $tasks,
'canCreate' => $request->user()->can('create', Task::class),
]);Escape every output
A user value can contain HTML or JavaScript. Printing it directly lets that content become active page code, creating an XSS vulnerability.
Escape titles, descriptions, names, and every dynamic value for its HTML context. Text, attributes, URLs, and scripts do not have exactly the same escaping rules.
Use raw HTML only for explicitly trusted and sanitized content. Database origin does not make a value safe; it may have been dangerous before storage.
Validate input for business rules; escape output for its display context.
<h2><?= e($task->title) ?></h2>
<p><?= e($task->description) ?></p>Render conditions and collections
A task list needs at least three states: successful loading with results, an empty collection, and sometimes a loading error. A robust view renders each state intentionally.
Inside the loop, give each item consistent structure and keep decisions presentational: show a completed badge, date, or permitted button. A rule such as deciding whether a task is overdue should ideally be prepared by the model.
Empty state is not a technical error. It guides the user toward a first action with useful copy and a creation link.
Every possible data state deserves an intentional presentation.
<?php if (empty($tasks)): ?>
<section class="empty-state">
<h2>No tasks yet</h2>
<a href="<?= route('tasks.create') ?>">Create your first task</a>
</section>
<?php else: ?>
<?php foreach ($tasks as $task): ?>
<?php view('tasks/_card', ['task' => $task]); ?>
<?php endforeach; ?>
<?php endif; ?>Extract reusable partials
A partial is presentation reused by several pages: header, navigation, footer, flash message, or task card. It removes duplication while preserving simple HTML.
Extract a fragment when it is a recognizable unit or when one change should apply everywhere. Do not put every tag in a separate file; excessive fragmentation makes pages harder to follow.
Pass the data a partial needs. A Task card receives a task and relevant permissions; it should not secretly find the user or query the database.
A good partial has one clear visual responsibility and a small data contract.
Build a shared layout
The layout contains the stable document skeleton: doctype, language, metadata, stylesheets, header, main area, and footer. Pages then provide specific content.
Centralizing this skeleton gives every page the same accessibility and security foundations. Title and description can still vary by route.
Load shared assets once and keep an explicit content slot. A layout should not hide business calls or require data that some pages do not have.
The layout centralizes shared structure without erasing each page's identity.
Build forms
The create form sends a new task to store. The edit form sends changes to update. Their action and method must match the routes defined in chapter 4.
Every field has a connected label, previous value, useful hint, and error area. The button describes the action: Create task or Save changes rather than vague Submit.
Include the CSRF token in every Web mutation. For PATCH and DELETE, use the framework's method override when browsers can only submit GET and POST directly.
A form is an interface to an HTTP contract, not merely a group of fields.
<form method="POST" action="<?= route('tasks.store') ?>">
<?= csrf_field() ?>
<label for="title">Task title</label>
<input id="title" name="title"
value="<?= e(old('title')) ?>"
aria-describedby="title-error">
<?php if ($errors->has('title')): ?>
<p id="title-error" role="alert">
<?= e($errors->first('title')) ?>
</p>
<?php endif; ?>
<button type="submit">Create task</button>
</form>Display validation errors
When input is invalid, users must understand what happened, where to fix it, and how to succeed. A red border without text is not enough.
Show a summary near the top when several errors exist, then a precise message beside each field. Associate messages with controls using appropriate accessibility attributes.
Restore previous input so users do not start over, except sensitive data such as passwords. Use human language rather than internal column names.
A useful error explains the problem and moves the user toward the solution.
Use flash messages
After a redirect, the new document no longer naturally knows the previous request's result. A flash message carries that information for one request.
Use it to confirm creation, update, or deletion, and sometimes to announce a warning. Keep it short, specific, and visible without blocking navigation.
A flash partial can style success, warning, or error. It must remain understandable without color alone and be announced properly to assistive technology.
Flash confirms a transition; it does not replace durable page content.
Test rendering and accessibility
A view test checks that important data appears, dangerous values are escaped, and empty and error states exist. It should not depend on every CSS class.
Test form links and actions against named routes. Also verify labels, heading hierarchy, document language, alternative text, and keyboard navigation.
Complement automation with real mobile and tablet reading. A technically valid page can remain unusable when a form overflows or an error is invisible.
Test what users can read, understand, and accomplish.
Guided workshop
Build every Task view.
- Create the layout with metadata, header, main, and footer.
- Build index with list and empty state.
- Extract a reusable task card.
- Create show, create, and edit.
- Add CSRF, old values, and accessible errors.
- Display flash messages after redirects.
- Test an XSS payload, empty state, and named links.
Reasoned solution
Read the page as a user and as an attacker.
Users must understand hierarchy, find the next action, and correct errors without losing input. Attackers must never turn a task title into active code. Verify both perspectives before considering the view complete.
Should every element become a partial?
No. Extract reused or conceptually independent units. Keep together HTML that is easier to understand together.
In summary
A successful view makes data safe, readable, and actionable.
You can now build a page without mixing presentation and business behavior. The controller supplies an explicit contract; the view escapes values, composes layout and partials, then adapts rendering to different data states.
- pass only required data
- escape every output for its context
- design result, empty, and error states
- use partials and layouts without excessive fragmentation
- make forms and errors accessible
- test visible behavior, not CSS details
In chapter 7, we will give these pages visual identity through CSS, progressive JavaScript, images, and a clean strategy for public assets and browser caching.