Welcome to this in-depth tutorial on Laravel’s Blade templating engine. Blade is a powerful, lightweight templating system included with Laravel that allows you to create dynamic, reusable views with minimal overhead. This tutorial covers all topics from the provided content, structured logically for easy learning. We’ll start with the basics and progress to advanced features.
We’ll use examples throughout, assuming you have a basic Laravel setup. Blade templates end with .blade.php and are stored in resources/views. Views can be returned from routes or controllers using the view() helper.
Introduction to Blade Templates
Blade is Laravel’s templating engine, designed to be simple yet powerful. Unlike some templating engines, Blade allows plain PHP code in templates. Templates are compiled to PHP and cached for performance, adding almost no overhead.
- File Structure: Store templates in
resources/viewswith.blade.phpextension. - Rendering Views: Use the global
view()helper in routes or controllers.
Example in a route:
Route::get('/', function () {
return view('greeting', ['name' => 'Finn']);
});
This renders resources/views/greeting.blade.php and passes the name variable.
Supercharging Blade With Livewire
To build dynamic interfaces without heavy JavaScript frameworks like React or Vue, integrate Laravel Livewire. Livewire enhances Blade components with reactive functionality, handling client-side updates seamlessly.
- Benefits: No build steps, client-side rendering, or complex setups.
- Getting Started: Install Livewire via Composer (
composer require livewire/livewire), then use it in Blade templates.
Example: Create a Livewire component with php artisan make:livewire Counter, then render it in Blade as <livewire:counter />.
For more, check the official Livewire documentation.
Displaying Data
Pass data to views via the view() helper’s second argument. Display it using double curly braces {{ }}, which automatically escapes output to prevent XSS attacks.
Example Route:
Route::get('/', function () {
return view('welcome', ['name' => 'Samantha']);
});
In welcome.blade.php:
Hello, {{ $name }}.
You can also echo PHP functions:
The current UNIX timestamp is {{ time() }}.
HTML Entity Encoding
By default, Blade double-encodes HTML entities using htmlspecialchars. To disable double-encoding globally, add this to AppServiceProvider::boot():
Blade::withoutDoubleEncoding();
Displaying Unescaped Data
Use {!! !!} to output unescaped data:
Hello, {!! $name !!}.
Warning: Be cautious with user-supplied data to avoid XSS. Prefer {{ }} for safety.
Blade and JavaScript Frameworks
JavaScript frameworks like Vue or React use curly braces, conflicting with Blade. Prefix with @ to escape:
<h1>Laravel</h1>
Hello, @{{ name }}.
This outputs: Hello, {{ name }}. (untouched by Blade).
To escape Blade directives in JS:
@@if() <!-- Outputs: @if() -->
Rendering JSON
To render arrays as JSON for JavaScript variables, use Js::from() (available via facade in recent Laravel versions):
<script>
var app = {{ Js::from($array) }};
</script>
This ensures proper escaping. Avoid complex expressions in the directive.
The @verbatim Directive
For large sections with JS variables, wrap in @verbatim to avoid prefixing each echo with @:
@verbatim
<div class="container">
Hello, {{ name }}.
</div>
@endverbatim
Blade Directives
Blade provides shortcuts for PHP control structures.
If Statements
@if (count($records) === 1)
I have one record!
@elseif (count($records) > 1)
I have multiple records!
@else
I don't have any records!
@endif
@unless (opposite of if):
@unless (Auth::check())
You are not signed in.
@endunless
@isset and @empty:
@isset($records)
// Defined and not null
@endisset
@empty($records)
// Empty
@endempty
Authentication Directives
@auth
// Authenticated
@endauth
@guest
// Guest
@endguest
With guards:
@auth('admin')
// Admin guard
@endauth
Environment Directives
@production
// In production
@endproduction
@env('staging')
// In staging
@endenv
@env(['staging', 'production'])
// In staging or production
@endenv
Section Directives
Check if a section exists:
@hasSection('navigation')
@yield('navigation')
@endif
@sectionMissing('navigation')
@include('default-navigation')
@endif
Session Directives
Display session value if exists:
@session('status')
<div>{{ $value }}</div>
@endsession
Context Directives
Similar to session, for context values:
@context('canonical')
<link href="{{ $value }}" rel="canonical">
@endcontext
Switch Statements
@switch($i)
@case(1)
First case...
@break
@case(2)
Second case...
@break
@default
Default case...
@endswitch
Loops
@for ($i = 0; $i < 10; $i++)
{{ $i }}
@endfor
@foreach ($users as $user)
<p>User {{ $user->id }}</p>
@endforeach
@forelse ($users as $user)
<li>{{ $user->name }}</li>
@empty
<p>No users</p>
@endforelse
@while (true)
<p>Looping...</p>
@endwhile
Use @continue and @break:
@foreach ($users as $user)
@continue($user->type == 1)
<li>{{ $user->name }}</li>
@break($user->number == 5)
@endforeach
The Loop Variable
In @foreach, access $loop:
$loop->index(0-based)$loop->iteration(1-based)$loop->remaining$loop->count$loop->first$loop->last$loop->even$loop->odd$loop->depth$loop->parent(for nested loops)
Example:
@foreach ($users as $user)
@if ($loop->first)
First iteration.
@endif
<p>User {{ $user->id }}</p>
@endforeach
Conditional Classes & Styles
@class for conditional classes:
<span @class([
'p-4',
'font-bold' => $isActive,
'text-gray-500' => ! $isActive,
'bg-red' => $hasError,
])></span>
@style for styles:
<span @style([
'background-color: red',
'font-weight: bold' => $isActive,
])></span>
Additional Attributes
@checked, @selected, @disabled, @readonly, @required:
<input type="checkbox" @checked($user->active) />
<select>
<option @selected($version == old('version'))>{{ $version }}</option>
</select>
<button @disabled($errors->isNotEmpty())>Submit</button>
Including Subviews
Include views with @include:
@include('shared.errors')
Pass extra data:
@include('view.name', ['status' => 'complete'])
Conditional includes:
@includeIf('view.name', ['status' => 'complete'])
@includeWhen($boolean, 'view.name', ['status' => 'complete'])
@includeUnless($boolean, 'view.name', ['status' => 'complete'])
@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])
Avoid __DIR__ and __FILE__ in views.
Rendering Views for Collections
Use @each for loops + includes:
@each('view.name', $jobs, 'job', 'view.empty')
- First arg: View per item
- Second: Collection
- Third: Variable name
- Fourth: Empty view (optional)
Child views don’t inherit parent variables; use @foreach + @include if needed.
The @once Directive
Evaluate once per render (e.g., for unique JS):
@once
@push('scripts')
<script>...</script>
@endpush
@endonce
Shortcuts: @pushOnce, @prependOnce.
Raw PHP
Embed PHP:
@php
$counter = 1;
@endphp
Import classes/functions/constants with @use:
@use('App\Models\Flight')
@use('App\Models\Flight', 'FlightModel')
@use('App\Models\{Flight, Airport}')
@use(function App\Helpers\format_currency)
@use(const App\Constants\MAX_ATTEMPTS)
@use(function App\Helpers\format_currency, 'formatMoney')
@use(function App\Helpers\{format_currency, format_date})
Comments
Blade comments are stripped from HTML:
{{-- This is a comment --}}
Components
Components are reusable UI pieces.
Creating Components
Class-based: php artisan make:component Alert (creates app/View/Components/Alert.php and resources/views/components/alert.blade.php).
Anonymous: php artisan make:component forms.input --view (only view).
Subdirectories: php artisan make:component Forms/Input.
Manually Registering Package Components
In service provider boot():
Blade::component('package-alert', Alert::class);
Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
Render: <x-package-alert/> or <x-nightshade::calendar/>.
Rendering Components
<x-alert/>
<x-inputs.button/>
Conditional render: Define shouldRender() in class.
Index Components
If directory name matches component file, render without repeating: <x-card> for app/View/Components/Card/Card.php.
Passing Data to Components
Via attributes:
<x-alert type="error" :message="$message"/>
In class constructor:
public function __construct(public string $type, public string $message) {}
Display in view: {{ $type }} {{ $message }}.
CamelCase in PHP, kebab-case in HTML.
Short syntax: <x-profile :$userId :$name />.
Escape for JS: Use :: prefix (e.g., ::class).
Component Methods
Call public methods in view:
public function isSelected(string $option): bool { ... }
<option {{ $isSelected($value) ? 'selected' : '' }}>...</option>
Accessing Attributes and Slots Within Component Classes
Return closure from render():
public function render(): Closure {
return function (array $data) {
// $data['componentName'], $data['attributes'], $data['slot']
return '<div {{ $attributes }}>Content</div>';
};
}
Additional Dependencies
Inject via constructor:
public function __construct(AlertCreator $creator, string $type, string $message) {}
Hiding Attributes / Methods
protected $except = ['type'];
Component Attributes
Access via $attributes bag:
<div {{ $attributes }}>...</div>
Merge defaults:
<div {{ $attributes->merge(['class' => 'alert']) }}>...</div>
Conditional classes:
<div {{ $attributes->class(['p-4', 'bg-red' => $hasError]) }}>...</div>
Chain: $attributes->class(['p-4'])->merge(['type' => 'button']).
Non-class merging: Overwrites unless using prepends.
Filter/retrieve:
$attributes->filter(fn ($value, $key) => $key == 'foo')$attributes->whereStartsWith('wire:model')$attributes->whereDoesntStartWith('wire:model')$attributes->first()$attributes->has('class')$attributes->has(['name', 'class'])$attributes->hasAny(['href', ':href'])$attributes->get('class')$attributes->only(['class'])$attributes->except(['class'])
Reserved keywords: Avoid data, render, etc., as property/method names.
Slots
Default slot: {{ $slot }}.
Named slots:
<x-alert>
<x-slot:title>Server Error</x-slot>
Content
</x-alert>
In component: {{ $title }} {{ $slot }}.
Check empty: @if ($slot->isEmpty()) ... @endif.
Has content: $slot->hasActualContent().
Scoped slots: Access component via $component in slot:
<x-slot:title>{{ $component->formatAlert('Error') }}</x-slot>
Slot attributes:
<x-slot:heading class="font-bold">Heading</x-slot>
Access: $heading->attributes->class(['text-lg']).
Inline Component Views
Return markup from render():
public function render(): string {
return <<<'blade'
<div>{{ $slot }}</div>
blade;
}
Generate: php artisan make:component Alert --inline.
Dynamic Components
<x-dynamic-component :component="$componentName" class="mt-4" />
Anonymous Components
Single file in resources/views/components/alert.blade.php: <x-alert/>.
Nested: <x-inputs.button/>.
Anonymous Index Components
Place accordion.blade.php in components/accordion/ for root render as <x-accordion>.
Data Properties / Attributes (Anonymous)
Use @props:
@props(['type' => 'info', 'message'])
<div class="alert-{{ $type }}">{{ $message }}</div>
Accessing Parent Data
Use @aware in child:
@aware(['color' => 'gray'])
<li class="text-{{ $color }}-800">{{ $slot }}</li>
Anonymous Component Paths
Register extra paths:
Blade::anonymousComponentPath(__DIR__.'/../components', 'dashboard');
Render: <x-dashboard::panel/>.
Building Layouts
Layouts Using Components
Define layout.blade.php:
<html>
<title>{{ $title ?? 'Default' }}</title>
<body>{{ $slot }}</body>
</html>
Use:
<x-layout>
<x-slot:title>Custom</x-slot>
Content
</x-layout>
Layouts Using Template Inheritance
Define layout app.blade.php:
<title>@yield('title')</title>
@section('sidebar') Sidebar @show
@yield('content')
Extend in child:
@extends('layouts.app')
@section('title', 'Page Title')
@section('sidebar')
@parent
Appended sidebar.
@endsection
@section('content')
Body content.
@endsection
Default yield: @yield('content', 'Default').
Forms
CSRF Field
<form method="POST">
@csrf
</form>
Method Field
Spoof PUT/PATCH/DELETE:
@method('PUT')
Validation Errors
<input class="@error('title') is-invalid @enderror" />
@error('title')
<div>{{ $message }}</div>
@enderror
@error('email', 'login') <!-- Specific bag -->
...
@enderror
With @else:
<class="@error('email') is-invalid @else is-valid @enderror"/>
Stacks
Push content (e.g., scripts):
@push('scripts')
<script src="/example.js"></script>
@endpush
@pushIf($shouldPush, 'scripts')
...
@endPushIf
Render: @stack('scripts').
Prepend: @prepend('scripts') ... @endprepend.
Service Injection
@inject('metrics', 'App\Services\MetricsService')
Monthly Revenue: {{ $metrics->monthlyRevenue() }}.
Rendering Inline Blade Templates
return Blade::render('Hello, {{ $name }}', ['name' => 'Julian']);
Delete cache: deleteCachedView: true.
Rendering Blade Fragments
For partial responses (e.g., with Turbo/htmx):
@fragment('user-list')
<ul>...</ul>
@endfragment
In controller: return view('dashboard')->fragment('user-list');
Conditional: ->fragmentIf($request->hasHeader('HX-Request'), 'user-list').
Multiple: ->fragments(['user-list', 'comment-list']).
Extending Blade
Custom Directives
In AppServiceProvider::boot():
Blade::directive('datetime', function ($expression) {
return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
});
Use: @datetime($var).
Clear cache: php artisan view:clear.
Custom Echo Handlers
Blade::stringable(function (Money $money) {
return $money->formatTo('en_GB');
});
Echo: {{ $money }}.
Custom If Statements
Blade::if('disk', function ($value) {
return config('filesystems.default') === $value;
});
Use:
@disk('local')
...
@elsedisk('s3')
...
@enddisk
