Mastering Laravel Blade Templates

aravel Laravel Views & Blade Templates – Beginner’s Guide

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/views with .blade.php extension.
  • 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