Laravel Blade: A Complete Guide to the Laravel Template Engine

Video thumbnail

Discover how to boost your interface development with Laravel Blade, the most powerful and elegant templating engine in the PHP ecosystem. 

In this practical guide, we will explore everything from basic control directives like @if and @foreach, to the advanced use of Components, Stacks, and Slots—essential tools for creating dynamic, reusable, and highly efficient views in your modern web projects.

You will learn to optimize your frontend performance using professional techniques such as single script loading with @once and smart stack management with @push. Whether you are taking your first steps or looking to refine your workflow with Inertia or Livewire, mastering custom directives and Blade's display logic will allow you to write much cleaner, safer, and easier-to-maintain code.

Blade is not just about components; in fact, that part is further up in the documentation. Approximately one-third of the page covers key functionalities, demonstrating how important Blade is within Laravel.

Although it is not one of my favorite technologies within Laravel (especially compared to Livewire or Inertia), it remains fundamental to understand.

This is the controller we will use for the practice:

class CourseController extends Controller
{
    public function index(Request $request)
    {
        return view('pruebas.dashboard', [
            'user' => 'Andrés Cruz',
            'role' => 'admin',
            'status' => 2, // 1: Pendiente, 2: Activo, 3: Suspendido
            'courses' => [
                ['id' => 1, 'name' => 'Laravel 13', 'type' => 'Backend', 'premium' => true],
                ['id' => 2, 'name' => 'Vue.js 3', 'type' => 'Frontend', 'premium' => false],
                ['id' => 3, 'name' => 'AWS Cloud', 'type' => 'DevOps', 'premium' => true],
            ],
            'tags' => [], // Array vacío para probar @forelse
            'isSubscribed' => true,
        ]);
    }
}

Master template:

html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>@yield('title', 'Plataforma Educativa')title>
    <script src="https://cdn.tailwindcss.com">script>
    @stack('styles')
head>
<body class="bg-slate-50 p-10">
    <header class="mb-5 p-4 bg-white shadow">
        {{-- Directiva de Autenticación --}}
        @auth
            <p>Conectado como: <strong>@yield('user_name')strong>p>
        @endauth
    header>
    <main class="container mx-auto">
        @yield('content')
    main>
    {{-- Directiva para inyectar scripts desde vistas hijas --}}
    @stack('scripts')
body>
html>

The characteristic feature is the @stack('scripts') which we explain later.

The view we will explore throughout the section:

@extends('pruebas.layout')
@section('title', 'Panel de Control - Blade 13')
@section('user_name', $user)
@section('content')
    <h1 class="text-2xl font-bold mb-6">Explorando Directivas de Bladeh1>
    {{-- 1. Condicionales If / Else --}}
    <section class="mb-8">
        <h2 class="text-lg font-semibold">Estado de la cuenta:h2>
        @if($status === 1)
            <span class="text-yellow-600">Esperando verificación...span>
        @elseif($status === 2)
            <span class="text-green-600">Cuenta verificada correctamente.span>
        @else
            <span class="text-red-600">Cuenta restringida.span>
        @endif
    section>
    {{-- 2. Directiva Switch --}}
    <section class="mb-8">
        @switch($role)
            @case('admin')
                <div class="bg-blue-100 p-2 text-blue-800">Acceso total de administradordiv>
                @break
            @case('editor')
                <div class="bg-green-100 p-2 text-green-800">Acceso de edicióndiv>
                @break
            @default
                <div class="bg-gray-100 p-2">Acceso de lecturadiv>
        @endswitch
    section>
    {{-- 3. Bucles y la variable $loop --}}
    <section class="mb-8">
        <h2 class="text-xl font-bold mb-3">Listado de Cursosh2>
        <ul class="space-y-2">
            @foreach($courses as $course)
                <li @class([
                    'p-3 rounded border',
                    'bg-yellow-50 border-yellow-200' => $course['premium'],
                    'bg-white border-gray-200' => !$course['premium']
                ])>
                    <strong>#{{ $loop->iteration }}strong> - {{ $course['name'] }} 
                    <span class="text-sm italic">({{ $course['type'] }})span>
                    @if($loop->first) <b class="text-blue-500">[NUEVO]b> @endif
                    @if($loop->last) <b class="text-gray-400">[FINAL]b> @endif
                li>
            @endforeach
        ul>
    section>
    {{-- 4. Forelse (Manejo de estados vacíos) --}}
    <section class="mb-8">
        <h2 class="text-lg font-semibold">Etiquetas del perfil:h2>
        <div class="flex gap-2">
            @forelse($tags as $tag)
                <span class="bg-gray-200 px-2 py-1">{{ $tag }}span>
            @empty
                <p class="text-gray-400 italic">No has definido etiquetas todavía.p>
            @endforelse
        div>
    section>
    {{-- 5. Directivas de Sesión y Errores (Útiles en formularios) --}}
    <section class="mb-8 border-t pt-4">
        @error('email')
            <div class="text-red-500 font-bold">Error en el correo: {{ $message }}div>
        @enderror
        {{-- Directiva de entorno --}}
        @env('local')
            <div class="text-xs text-orange-400 mt-4 italic">
                * Estás viendo esto porque el entorno es Local.
            div>
        @endenv
    section>
    {{-- 7. No toques nada de lo que hay aquí dentro, ignora las llaves {{ }} y déjalas tal cual --}}    
    @verbatim
        <div id="app">
            <h1>Perfil de Usuarioh1>
            <p>Nombre: {{ user.name }}p>
            <p>Email: {{ user.email }}p>
            <p>Bio: {{ user.bio }}p>
        div>
    @endverbatim
            Hola, @{{ nombre_en_js }}.
            Tu edad es @{{ edad_en_js }}.
            Tu ciudad es @{{ ciudad_en_js }}.
    {{-- 8. PHP nativo (Solo cuando es estrictamente necesario) --}}
    @php
        $currentTime = now()->format('H:i');
    @endphp
    <p class="mt-10 text-xs">Generado a las: {{ $currentTime }}p>
    @foreach($courses as $c)
        <div>
            <h3>{{ $c['name'] }}h3>
            <canvas id="grafico-{{ $c['id'] }}">canvas>
        div>
        {{-- Usamos ONCE para que el script de la librería no se repita 100 veces --}}
        @once
            @push('scripts')
                <script src="https://cdn.jsdelivr.net/npm/chart.js">script>
                <script>
                    console.log("La librería Chart.js se cargó solo una vez.");
                script>
            @endpush
        @endonce
        {{-- Este PUSH no tiene ONCE porque cada gráfico sí necesita su propia inicialización --}}
        @push('scripts')
            <script>
                new Chart(document.getElementById('grafico-{{ $c["id"] }}'), { ... });
            script>
        @endpush
    @endforeach
@endsection
@push('scripts')
    <script>
        console.log('Blade ha renderizado la vista correctamente.');
    script>
@endpush

What I like most, beyond all this integration, is that Blade has also improved significantly.

Every time it feels more like Vue, which I love, obviously.

Printing HTML

Everything starts with simple data printing from the server, just like in Vue (I mention it because it's the framework I use most):

{{ $name }}

Although Angular, React, Astro, etc., also work similarly in this basic part.

In addition to printing, Blade has conditionals, and what I like is that there are also specific conditionals, such as @auth or @guest, to verify if the user is authenticated or a guest.

Control Structures and Conditionals

Blade makes PHP much more expressive and cleaner in the HTML.

@if and @switch Conditionals

We can use the classic @if, @else, and @elseif.

    {{-- 1. Condicionales If / Else --}}
    <section class="mb-8">
        <h2 class="text-lg font-semibold">Estado de la cuenta:h2>
        @if($status === 1)
            <span class="text-yellow-600">Esperando verificación...span>
        @elseif($status === 2)
            <span class="text-green-600">Cuenta verificada correctamente.span>
        @else
            <span class="text-red-600">Cuenta restringida.span>
        @endif
    section>

Remember that you can use the triple equals (===) to also compare by type, which is very useful in PHP as it is a loosely typed language (where a string can be compared to an integer).

@switch: It is ideal when you have multiple cases, such as user roles (admin, editor, reader).

    {{-- 2. Directiva Switch --}}
    
class="mb-8"> @switch($role) @case('admin') <div class="bg-blue-100 p-2 text-blue-800">Acceso total de administradordiv> @break @case('editor') <div class="bg-green-100 p-2 text-green-800">Acceso de edicióndiv> @break @default <div class="bg-gray-100 p-2">Acceso de lecturadiv> @endswitch

There are also directives like @empty, @isset, among others. These "helpers" make the code much cleaner and easier to read:

@isset($records)
    // $records is defined and is not null...
@endisset
 
@empty($records)
    // $records is "empty"...
@endempty

Of course, all this can be done with a classic if and PHP functions, but this is more elegant, especially when you are in Blade and need to show conditional content in a simple and readable way.

The @foreach Loop and the $loop Variable

In a @foreach, Laravel gives us the magic $loop variable. With it, you can know:

  • $loop->first: If it's the first element (useful for adding labels like "[NEW]").
  • $loop->last: If it's the last element.
  • $loop->iteration: The current index of the traversal.
    {{-- 3. Bucles y la variable $loop --}}
    <section class="mb-8">
        <h2 class="text-xl font-bold mb-3">Listado de Cursosh2>
        <ul class="space-y-2">
            @foreach($courses as $course)
                <li @class([
                    'p-3 rounded border',
                    'bg-yellow-50 border-yellow-200' => $course['premium'],
                    'bg-white border-gray-200' => !$course['premium']
                ])>
                    <strong>#{{ $loop->iteration }}strong> - {{ $course['name'] }} 
                    <span class="text-sm italic">({{ $course['type'] }})span>
                    @if($loop->first) <b class="text-blue-500">[NUEVO]b> @endif
                    @if($loop->last) <b class="text-gray-400">[FINAL]b> @endif
                li>
            @endforeach
        ul>
    section>

The Powerful @forelse

It is the perfect solution for lists. If the collection has data, it iterates through them; if it's empty, it executes the @empty block. This prevents the interface from looking bad or empty when there is no content.

  {{-- 4. Forelse (Manejo de estados vacíos) --}}
    <section class="mb-8">
        <h2 class="text-lg font-semibold">Etiquetas del perfil:h2>
        <div class="flex gap-2">
            @forelse($tags as $tag)
                <span class="bg-gray-200 px-2 py-1">{{ $tag }}span>
            @empty
                <p class="text-gray-400 italic">No has definido etiquetas todavía.p>
            @endforelse
        div>
    section>

Error Handling and Forms

With the @error directive, you can display errors directly:

{{-- 5. Directivas de Sesión y Errores (Útiles en formularios) --}}
    <section class="mb-8 border-t pt-4">
        @error('email')
            <div class="text-red-500 font-bold">Error en el correo: {{ $message }}div>
        @enderror

This greatly simplifies validation handling in forms.

Environment Variables

You can use @env to detect the environment:

 {{-- Environment directive --}}
 @env('local')
   <div class="text-xs text-orange-400 mt-4 italic">
      * Estás viendo esto porque el entorno es Local.
    div>
@endenv

Frontend Integration (@verbatim)

When working with frameworks like Vue, you can use @verbatim to avoid conflicts with {{ }} braces.

This is very useful when rendering is handled by the frontend.

If you use frameworks like Vue or Alpine, you can use @verbatim so Blade ignores the {{ }} braces and allows the JavaScript framework to process them. You can also use @{{ }} to escape a single line.

    {{-- 7. No toques nada de lo que hay aquí dentro, ignora las llaves {{ }} y déjalas tal cual --}}    
    @verbatim
        <div id="app">
            <h1>Perfil de Usuarioh1>
            <p>Nombre: {{ user.name }}p>
            <p>Email: {{ user.email }}p>
            <p>Bio: {{ user.bio }}p>
        div>
    @endverbatim
            Hola, @{{ nombre_en_js }}.
            Tu edad es @{{ edad_en_js }}.
            Tu ciudad es @{{ ciudad_en_js }}.

Using PHP inside Blade

You can use @php, though in moderation:

@php
   $currentTime = now()->format('H:i');
@endphp

Ideally, logic should be handled in the controller, but in specific cases, it can be useful.

Advanced Directives: Scripts and Stacks

The @once directive allows a block to be executed only once, even within loops.

This is key to avoid repeating scripts unnecessarily.

Together with @push and @stack, you can better organize your scripts:

  • @push('scripts') adds content
  • @stack('scripts') renders it in the layout

1. The @once Directive

This directive ensures that the internal code is rendered only once, regardless of how many times a loop repeats. It is extremely useful for loading JavaScript libraries (like Chart.js) only once, even if you have ten charts on the page.

2. @push and @stack

Unlike @yield, which is typically used for a single block, @stack works like a pile of books.

  • In the master layout, you define the @stack('scripts').
  • From any view or component, you use @push('scripts').

Laravel will stack all the scripts at the end of the page, keeping everything organized.

{{-- In the Layout --}} 
@stack('scripts') {{-- Anywhere in the view --}} 
@push('scripts') 
   <script>console.log("This script is added to the stack");script> @endpush

Dynamic Classes and Attributes

Video thumbnail

One of the best recent additions is the @class directive. It is much cleaner and more expressive than doing manual string concatenations inside a class="".

As I mentioned when I did a brief analysis of Laravel version 12, one of the things I like most about this new version is that I feel everything is a bit more integrated:

Laravel is NO LONGER *just* a PHP Framework

Now we don't just have, for example, Laravel, where if you want to use Vue you have to use a package with Jetstream, Inertia, and all those "dramas," but everything already feels like part of the same system.

And that “something more” is a quite interesting combination of several technologies, depending on what you want to use.

  • If you want to use React, you already have a prepared scaffolding.
  • If you want to use Vue, it's also ready. There are even community projects (a topic I also talked about in another video; if I did, I'll leave the card here) where you can use other frameworks like Svelte, among others.
  • And obviously, there is also Alpine. I think I didn't mention it before, but React, Alpine, and Vue are the ones that come by default. And of course, pure Laravel, without any extras, or as I call it: Base Laravel.
  • @class([ 'p-3 rounded border', 'bg-yellow-50 border-yellow-200' => $course['premium'], 'bg-white border-gray-200' => !$course['premium'] ])>
  • Or styles:

    'background-color: red',
        'font-weight: bold' => $isActive,
    ])>
     
    "background-color: red; font-weight: bold;">

    Attributes like checked, disabled, required, or readonly:

    <input
        type="email"
        name="email"
        value="email@laravel.com"
        @readonly($user->isNotAdmin())
    />
    <input
        type="checkbox"
        name="active"
        value="active"
        @checked(old('active', $user->active))
    />
    <button type="submit" @disabled($errors->isNotEmpty())>Submit
    <input
        type="text"
        name="title"
        value="title"
        @required($user->isAdmin())
    />

    Security and HTML Data Escaping

    HTML (HyperText Markup Language) is a markup language used to create web pages and, of course, this includes web applications. It is a standard that provides a structure based on XML-based syntax to organize all the content of a web page. HTML uses different types of tags; HTML is NOT a programming language since no logic is implemented in HTML; instead, it is a markup language where what you place is what you get.

    Printing HTML in Blade

    Many times we want to show an HTML block directly in a Blade view file; which by default, with:

    {{ $html }}

    To escape the HTML, we have:

    {!! $html !!}

    And remember to pass the data from the controller:

    class TestController extends Controller
    {
        public function index()
        {
            return view("index",['name' => 'Andres Cruz','html' => '

    Titulo

    ']);    } }

    Never use this with content that comes from users (like comments).

    The @include Directive and its Variants

    The most basic form is @include on its own. By default, the view you bring in will "inherit" all the variables you already have on the main page, but you can also pass extra data if needed.

    {{-- We include an error view and pass an additional piece of data --}} <div>
       @include('shared.errors', ['tipo' => 'critico'])
       <form>
           form>
    div>

    Controlling Existence and Conditions

    Sometimes we are not sure if a view exists (for example, if you allow custom themes) or we want to show it only if something is met. For that, we have these gems:

    • @includeIf: If the view does not exist, Laravel simply does nothing instead of throwing a 500 error in your face.
    • @includeWhen: Included only if the condition is true.
    • @includeUnless: The opposite; included only if the condition is false.
    • @includeFirst: You pass an array of views and Laravel will render the first one it finds. Great for customizations!
    {{-- Only if the user is an admin, we include the control panel --}} 
    @includeWhen($user->isAdmin(), 'admin.panel', ['status' => 'complete']) {{-- Tries to load the custom design; if not, uses the default one --}} 
    @includeFirst(['custom.header', 'defaults.header'], ['status' => 'complete'])

    @includeIsolated: Shielding Your View

    This is new and very powerful. If you want the view you are including to know nothing about what's happening in the parent view (to avoid side effects or variable mixing), use @includeIsolated. It will only see what you explicitly pass to it.

    {{-- This view will NOT see the controller variables, only $user --}} 
    @includeIsolated('view.name', ['user' => $user])

    Rendering Collections with @each

    If you have a list (like the courses or posts we saw earlier), instead of doing a @foreach and an @include inside, you can do it all in a single line with @each.

    It is super efficient and even allows you to define a view for when the list is empty (like the @empty in @forelse).

    {{-- 1. View to render, 2. Collection, 3. Variable in the view, 4. Empty view --}} 
    @each('partials.job_card', $jobs, 'job', 'partials.no_jobs')
    Master Laravel Blade from scratch. Learn to use directives like @if, @foreach, and @forelse, manage scripts with @push, and optimize your views with dynamic components and classes.

    I agree to receive announcements of interest about this Blog.

    Andrés Cruz

    ES En español