Laravel Views and the Blade Template Engine
Blade is Laravel's "layout engine"—this is where data is transformed into the pages users see, and where layouts, components, and inheritance eliminate repetitive page code.
1. What You'll Learn
- Blade layout inheritance: @extends, @section, @yield
- Control Structures: @if/@foreach/@switch/@auth/@guest
- Blade Components: Class Components vs. Anonymous Components
- View Composers: Sharing Data
- Front-end assets: Vite compiles Tailwind CSS / Alpine.js
2. A True Story of a Front-End Developer
(1) Pain Point: Having to write the navigation bar repeatedly on every page
Charlie wrote 10 pages for ShopMetrics, manually copying 60 lines of navigation bar HTML into each one. When Bob asked to move the "Pricing" link from the navigation bar to a drop-down menu, Charlie had to edit all 10 files one by one—but when he got to the seventh file, he missed a class, causing the navigation bar styles to be inconsistent on the live site, which led to three complaints from Alice.
(2) Solutions for the Blade Layout
Blade uses @extends to inherit the layout and @section to populate the content; the navigation bar is defined only once and is automatically synchronized across the entire site.
// resources/views/layouts/app.blade.php — Define layout once
<body>
<nav>...</nav> <!-- Navigation appears on every page -->
@yield('content') <!-- Each page fills this slot -->
</body>
// resources/views/shops/index.blade.php — Extend and fill
@extends('layouts.app')
@section('content')
<h1>All Shops</h1>
@endsection
(3) Revenue
After Charlie implemented the Blade layout, the navigation bar only required maintenance of a single file. Bob's change request went from "modifying 10 files" to "modifying 1 file," with zero omissions.
3. Blade Layout Inheritance
(1) Defining the Layout
<!-- resources/views/layouts/app.blade.php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>@yield('title', 'ShopMetrics')</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
@include('partials.navbar')
<main class="container mx-auto">
@yield('content')
</main>
@include('partials.footer')
</body>
</html>
(2) Inheriting Layouts
<!-- resources/views/shops/index.blade.php -->
@extends('layouts.app')
@section('title', 'All Shops')
@section('content')
<div class="grid grid-cols-3 gap-4">
@foreach($shops as $shop)
<div class="card">{{ $shop->name }}</div>
@endforeach
</div>
@endsection
| Command | Function | Analogy |
|---|---|---|
@extends |
Specify the layout to inherit from | Select a template |
@section |
Define content blocks | Fill in the blanks in the template |
@yield |
Output content block | Placeholder in the template |
@include |
Import Subview | Insert Public Snippet |
(1) ▶ Example: ShopMetrics Two-Column Layout
<!-- resources/views/layouts/dashboard.blade.php -->
@extends('layouts.app')
@section('content')
<div class="flex">
@include('partials.sidebar')
<div class="flex-1 p-6">
@yield('dashboard-content')
</div>
</div>
@endsection
<!-- resources/views/dashboard/analytics.blade.php -->
@extends('layouts.dashboard')
@section('title', 'Analytics Dashboard')
@section('dashboard-content')
<h1>Analytics Overview</h1>
<div id="chart"></div>
@endsection
Output:
// Execution Successful
4. Control Structures
(1) Conditional Statements
@if($shop->is_active)
<span class="badge-green">Active</span>
@elseif($shop->is_suspended)
<span class="badge-yellow">Suspended</span>
@else
<span class="badge-gray">Inactive</span>
@endif
@auth
<a href="{{ route('dashboard') }}">Dashboard</a>
@endauth
@guest
<a href="{{ route('login') }}">Login</a>
@endguest
(2) Loop Statements
@foreach($shops as $shop)
<tr>
<td>{{ $shop->name }}</td>
<td>{{ $shop->revenue }}</td>
</tr>
@if($loop->last)
</tbody></table>
@endif
@endforeach
@forelse($orders as $order)
<li>{{ $order->total }}</li>
@empty
<li>No orders yet.</li>
@endforelse
| Loop Variable | Description |
|---|---|
$loop->index |
Current Index (starting at 0) |
$loop->iteration |
Current Round (starting from 1) |
$loop->first |
Is this the first one? |
$loop->last |
Is this the last one? |
$loop->count |
Total |
(1) ▶ Example: Conditional Rendering in the ShopMetrics Dashboard
<!-- resources/views/dashboard/index.blade.php -->
@extends('layouts.dashboard')
@section('dashboard-content')
@auth
<h1>Welcome, {{ auth()->user()->name }}!</h1>
@endauth
@if($shops->count() > 0)
<div class="grid grid-cols-3 gap-4">
@foreach($shops as $shop)
<div class="card {{ $loop->first ? 'border-blue-500' : '' }}">
<h3>{{ $shop->name }}</h3>
<p>Revenue: ${{ number_format($shop->revenue, 2) }}</p>
</div>
@endforeach
</div>
@else
<p>No shops yet. <a href="{{ route('shops.create') }}">Create one!</a></p>
@endif
@endsection
Output:
// Execution Successful
5. Blade Component
(1) Anonymous Components
<!-- resources/views/components/shop-card.blade.php -->
@props(['shop', 'highlight' => false])
<div class="card {{ $highlight ? 'border-gold' : '' }}">
<h3>{{ $shop->name }}</h3>
<p>{{ $shop->description }}</p>
{{ $slot }}
</div>
<!-- Usage -->
<x-shop-card :shop="$shop" :highlight="true">
<p>Featured this week!</p>
</x-shop-card>
(2) Class Components
php artisan make:component Alert
# Creates: app/View/Components/Alert.php
# And: resources/views/components/alert.blade.php
// app/View/Components/Alert.php
class Alert extends Component
{
public function __construct(
public string $type = 'info',
public ?string $message = null,
) {}
public function render(): View
{
return view('components.alert');
}
}
<!-- resources/views/components/alert.blade.php -->
<div class="alert alert-{{ $type }}">
{{ $message ?? $slot }}
</div>
<!-- Usage -->
<x-alert type="success" message="Shop created!" />
<x-alert type="warning">Check your settings.</x-alert>
| Dimension | Anonymous | Class |
|---|---|---|
| File | Blade Templates Only | PHP Classes + Blade Templates |
| Logic | None/Minimal | May include business logic |
| Props | @props() Declaration |
Constructor Parameters |
| Suitable for | Simple display components | Components requiring logic |
(1) ▶ Example: ShopMetrics Statistics Card Component
<!-- resources/views/components/stat-card.blade.php -->
@props(['label', 'value', 'icon', 'trend' => null])
<div class="bg-white rounded-lg shadow p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">{{ $label }}</p>
<p class="text-2xl font-bold">{{ $value }}</p>
@if($trend)
<p class="text-sm {{ $trend > 0 ? 'text-green-500' : 'text-red-500' }}">
{{ $trend > 0 ? '+' : '' }}{{ $trend }}%
</p>
@endif
</div>
<span class="text-3xl">{{ $icon }}</span>
</div>
</div>
<!-- Usage in dashboard -->
<x-stat-card label="Total Revenue" value="$12,450" icon="$" :trend="15.3" />
<x-stat-card label="Active Shops" value="23" icon="#" :trend="-2.1" />
Output:
// Execution Successful
6. View Composer
View Composers automatically inject shared data into views, eliminating the need to pass it repeatedly in each controller method.
// app/Providers/AppServiceProvider.php
public function boot(): void
{
// Share tenant info with all dashboard views
View::composer('dashboard.*', function ($view) {
$view->with('tenant', Tenant::current());
});
// Share navigation data with specific views
View::composer(['layouts.app', 'shops.*'], NavigationComposer::class);
}
| Type | When to Execute | Purpose |
|---|---|---|
View::composer() |
Each time the view is rendered | Dynamically calculate data |
View::creator() |
When instantiating a view | Bind data earlier |
View::share() |
All Views | Global Constants |
(1) ▶ Example: ShopMetrics Navigation View Composer
// app/View/Composers/NavigationComposer.php
class NavigationComposer
{
public function compose(View $view): void
{
$view->with([
'navShops' => auth()->check()
? auth()->user()->shops()->take(5)->get()
: collect(),
'navNotifications' => auth()->check()
? auth()->user()->unreadNotifications()->count()
: 0,
]);
}
}
// Register in AppServiceProvider
View::composer('layouts.app', NavigationComposer::class);
Output:
// Execution Successful
7. Front-End Assets: Vite
Laravel 11 uses Vite by default to compile front-end assets.
(1) Install Tailwind CSS and Alpine.js
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
npm install alpinejs
(2) Configure Vite
// vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: [
'resources/css/app.css',
'resources/js/app.js',
],
refresh: true,
}),
],
});
(3) Introduction to Blade
<!-- In layout head -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
| Command | Function |
|---|---|
npm run dev |
Start the Vite development server (hot reload) |
npm run build |
Compile Production Assets (Compressed) |
(1) ▶ Example: ShopMetrics Navigation with Alpine.js Interactions
<!-- resources/views/partials/navbar.blade.php -->
<nav class="bg-white shadow" x-data="{ open: false }">
<div class="container mx-auto px-4">
<div class="flex justify-between h-16">
<a href="{{ route('home') }}" class="font-bold text-xl">ShopMetrics</a>
<div class="flex items-center">
@auth
<div class="relative" @click="open = !open">
<button class="flex items-center">
{{ auth()->user()->name }}
@if($navNotifications > 0)
<span class="badge-red">{{ $navNotifications }}</span>
@endif
</button>
<div x-show="open" x-transition class="dropdown">
<a href="{{ route('dashboard') }}">Dashboard</a>
<a href="{{ route('shops.index') }}">Shops</a>
<form action="{{ route('logout') }}" method="POST">
@csrf
<button type="submit">Logout</button>
</form>
</div>
</div>
@else
<a href="{{ route('login') }}">Login</a>
@endauth
</div>
</div>
</div>
</nav>
Output:
// Execution Successful
8. Comprehensive Example: ShopMetrics Dashboard Page
<!-- ============================================
Comprehensive: ShopMetrics Dashboard Page
Covers: layout inheritance, components, directives, Alpine.js
============================================ -->
<!-- resources/views/dashboard/index.blade.php -->
@extends('layouts.dashboard')
@section('title', 'Dashboard — ShopMetrics')
@section('dashboard-content')
<div class="space-y-6" x-data="{ period: '7d' }">
<!-- Period Selector -->
<div class="flex gap-2">
<button @click="period = '7d'"
:class="period === '7d' ? 'btn-primary' : 'btn-secondary'">7 Days</button>
<button @click="period = '30d'"
:class="period === '30d' ? 'btn-primary' : 'btn-secondary'">30 Days</button>
<button @click="period = '90d'"
:class="period === '90d' ? 'btn-primary' : 'btn-secondary'">90 Days</button>
</div>
<!-- Stat Cards -->
<div class="grid grid-cols-4 gap-4">
<x-stat-card label="Total Revenue" value="${{ number_format($totalRevenue, 0) }}" icon="$" :trend="$revenueTrend" />
<x-stat-card label="Orders" value="{{ number_format($orderCount) }}" icon="#" :trend="$orderTrend" />
<x-stat-card label="Active Shops" value="{{ $activeShops }}" icon="#" :trend="$shopTrend" />
<x-stat-card label="Conversion Rate" value="{{ number_format($conversionRate, 1) }}%" icon="%" :trend="$conversionTrend" />
</div>
<!-- Recent Orders -->
<div class="bg-white rounded-lg shadow">
<h2 class="p-4 border-b font-semibold">Recent Orders</h2>
@forelse($recentOrders as $order)
<div class="p-4 border-b flex justify-between">
<span>{{ $order->product_name }}</span>
<span>${{ number_format($order->total, 2) }}</span>
</div>
@empty
<p class="p-4 text-gray-500">No orders in this period.</p>
@endforelse
</div>
</div>
@endsection
❓ FAQ
{{ dd($variable) }} to dump and pause the page directly; or install the Laravel Debugbar extension to view all variables, SQL queries, and memory usage.view('xxx', ['key' => $value]), which is suitable for data specific to that view; View Composers automatically inject shared data, which is suitable for data needed by multiple views (such as the navigation bar or the number of notifications).📖 Summary
- Blade uses @extends, @section, and @yield to implement layout inheritance and eliminate duplicate HTML
- Control directives @if, @foreach, @auth, and @guest are used to handle conditional logic in templates
- Anonymous Components are suitable for simple components, while Class Components are suitable for components with logic
- View Composers automatically inject shared data into views, eliminating the need for controllers to pass values repeatedly
- Vite is the default front-end build tool for Laravel 11 and supports hot reloading
- {{ }} automatically escapes characters to prevent XSS; use {!! !!} with caution when outputting raw HTML
📝 Exercises
-
Basic Exercise (⭐): Create a
layouts/app.blade.phplayout file for ShopMetrics that includes a navigation bar and footer, then create a homepagehome/index.blade.phpthat inherits from this layout and populate it with content. -
Advanced Exercise (⭐⭐): Create a
<x-alert>Blade component that supports atype(success/warning/error) and slot content, to be used in ShopMetrics scenarios where creation is successful or validation fails. -
Challenge (⭐⭐⭐): Use Alpine.js to implement a collapsible sidebar component, integrate it into a dashboard layout, support automatic collapse on mobile devices, and save the state to localStorage.



