Blade w Laravel – szablony, layouty, komponenty i sloty

Mateusz Bogolubow

04-11-2026

Czym jest Blade, jak działa `{{ }}`, jak zbudować layout i komponent wielokrotnego użytku oraz kiedy użyć `@include`. Z przykładami uruchomionymi w Laravelu.

Blade w Laravel – szablony, layouty, komponenty i sloty
Blade to wbudowany w Laravel system szablonów: pliki .blade.php zawierają zwykły HTML wzbogacony o dyrektywy (@if, @foreach, @extends) i wyrażenia {{ }}, które Laravel kompiluje do czystego PHP i zapisuje w pamięci podręcznej. Dane wypisane przez {{ }} są automatycznie escapowane, co chroni przed XSS, a layouty, komponenty i sloty pozwalają nie powtarzać tego samego HTML-a w wielu widokach.

Każda aplikacja webowa wcześniej czy później wyświetla ten sam fragment strony w kilkudziesięciu miejscach: nagłówek, menu, stopkę, kartę z produktem. Wklejanie go za każdym razem kończy się tym, że zmiana jednego przycisku wymaga poprawek w trzydziestu plikach. W Laravelu rozwiązuje to Blade, czyli system szablonów, którego używasz od pierwszego widoku w projekcie. Ten artykuł pokazuje, jak działa, i prowadzi od {{ }} po komponenty ze slotami. Wszystkie przykłady uruchomiliśmy w świeżej aplikacji (Laravel 13.40, PHP 8.4), a wyniki w tekście pochodzą z tego uruchomienia.

Czym jest Blade i po co go używać w Laravelu?

Blade to silnik szablonów Laravela. Widok to plik w katalogu resources/views z rozszerzeniem .blade.php, w którym piszesz zwykły HTML i dokładasz do niego dyrektywy zaczynające się od @. Dzięki temu nie mieszasz <?php echo ... ?> z kodem HTML, a szablon pozostaje czytelny dla osoby, która zna głównie HTML.

Co istotne, Blade nie jest interpretowany przy każdym żądaniu. Laravel kompiluje każdy szablon do zwykłego pliku PHP, zapisuje go w storage/framework/views i używa go ponownie, dopóki oryginał się nie zmieni. Dlatego Blade nie spowalnia aplikacji. W praktyce {{ $html }} zamienia się w <?php echo e($html); ?>, a {!! $html !!} w <?php echo $html; ?>, i właśnie ta różnica jest najważniejszą rzeczą, jaką musisz zapamiętać o Blade (wyjaśniamy ją niżej).

Widok zwracasz z kontrolera lub trasy funkcją view(), podając nazwę pliku bez rozszerzenia i tablicę danych:

Route::get('/posts', function () {
    return view('posts', [
        'title' => 'Posty',
        'posts' => [
            ['title' => 'Pierwszy wpis', 'featured' => true],
            ['title' => 'Drugi wpis', 'featured' => false],
        ],
    ]);
});

Klucze tablicy stają się zmiennymi w szablonie: 'title' to $title, a 'posts' to $posts. To jest odpowiedź na jedno z najczęściej wyszukiwanych pytań o Blade, czyli jak przekazać zmienną do widoku. Alternatywą jest zapis view('posts')->with('title', 'Posty').

Jak wypisać dane w Blade i czym różni się {{ }} od {!! !!}?

Dwoma nawiasami {{ $zmienna }} wypisujesz wartość z escapowaniem (escaping): znaki specjalne HTML zamieniają się na encje, więc przeglądarka wyświetli je jako tekst, a nie wykona. Nawiasy {!! $zmienna !!} wypisują wartość bez escapowania, dokładnie tak, jak ją dostały.

Różnicę najlepiej widać na przykładzie. Dla zmiennej $html = '<script>alert(1)</script>' szablon:

Escaped: {{ $html }}
Raw: {!! $html !!}

wyrenderował się tak:

Escaped: &lt;script&gt;alert(1)&lt;/script&gt;
Raw: <script>alert(1)</script>

Pierwsza linia jest bezpieczna, bo przeglądarka pokaże napis <script>alert(1)</script> jako tekst. W drugiej przeglądarka wykona skrypt. Jeśli $html pochodzi od użytkownika (komentarz, nazwa profilu, treść z formularza), {!! !!} otwiera drogę do ataku XSS, o którym piszemy w artykule XSS w JavaScript: co to jest i jak się przed nim bronić.

Zasada jest prosta: domyślnie zawsze {{ }}. Sięgasz po {!! !!} tylko wtedy, gdy wartość pochodzi od ciebie albo została wcześniej oczyszczona (np. HTML wygenerowany przez bibliotekę, której ufasz). Pytanie „dlaczego Blade pokazuje mi znaczniki HTML jako tekst” prawie zawsze znaczy, że dane są poprawnie escapowane, a nie że coś jest zepsute.

W {{ }} możesz używać dowolnego wyrażenia PHP, nie tylko zmiennej. Przydaje się operator ??, który podstawia wartość domyślną, gdy zmiennej nie ma:

Default: {{ $missing ?? 'domyślna' }}

Wynik: Default: domyślna. Bez ?? odwołanie do nieistniejącej zmiennej zgłosi błąd Undefined variable $html. Jeśli zobaczysz go w Laravelu, najpierw sprawdź, czy kontroler na pewno przekazał tę zmienną, a nie czy szablon jest źle napisany.

Co zrobić, gdy sam szablon ma zawierać literalne {{ }}, na przykład dla frameworka JavaScript, który używa tych samych nawiasów? Postaw przed nimi znak @. Wiersz Literal: @{{ name }} wyrenderował się jako Literal: {{ name }}, więc Blade zostawił go w spokoju.

Jakie dyrektywy Blade są najważniejsze?

Dyrektywy to słowa zaczynające się od @, które zastępują PHP-owe if, foreach i podobne. Na początek wystarczą te z poniższej tabeli.

DyrektywaDo czego służyOdpowiednik w PHP
@if, @elseif, @else, @endifwarunekif
@unlesswarunek odwrócony („jeśli nie”)if (!...)
@isset, @emptysprawdzenie, czy zmienna istnieje lub jest pustaisset(), empty()
@foreach, @for, @whilepętleforeach, for, while
@forelse ... @emptypętla z gałęzią na pustą listęforeach + if (empty())
@switch, @case, @breakwybór jednej z wielu wartościswitch
@php ... @endphpsurowy blok PHP<?php ... ?>
@json($tablica)zapis tablicy jako JSON do skryptujson_encode()

Przykład z kilkoma z nich, wyrenderowany w naszej aplikacji:

@php $n = 3; @endphp
n={{ $n }}
@unless ($n > 5) mniejsze @endunless
@switch($n) @case(3) trzy @break @default inne @endswitch
@json($arr)

Wynik dla $arr = ['a' => 1]: n=3 mniejsze trzy {"a":1}. Zwróć uwagę, że @php służy do drobnych rzeczy. Jeśli w szablonie zaczyna ci się pojawiać logika (zapytania do bazy, obliczenia), przenieś ją do kontrolera lub modelu. Widok ma wyświetlać dane, nie je przygotowywać.

Jak w Blade zrobić pętlę i obsłużyć pustą listę?

Najwygodniejsza jest @forelse, bo w jednej konstrukcji obsługuje listę i brak elementów:

@forelse ($posts as $post)
    <li @class(['post', 'featured' => $post['featured']])>
        {{ $loop->iteration }}/{{ $loop->count }}: {{ $post['title'] }}
        @if ($loop->first) (pierwszy) @endif
        @if ($loop->last) (ostatni) @endif
    </li>
@empty
    <p>Brak postów.</p>
@endforelse

Dla dwóch wpisów dostaliśmy:

<li class="post featured">
 1/2: A
 (pierwszy) </li>
<li class="post">
 2/2: B
 (ostatni) </li>

A dla pustej tablicy $posts pojawił się tylko akapit Brak postów. Wewnątrz każdej pętli Blade udostępnia zmienną $loop z informacjami o bieżącej iteracji: $loop->iteration (numer od 1), $loop->count (liczba elementów), $loop->first i $loop->last. Dzięki niej nie liczysz indeksów ręcznie. Dyrektywa @class buduje atrybut class z tablicy: element bez klucza jest zawsze dodawany, a element z kluczem tylko wtedy, gdy warunek jest prawdziwy.

Jak zbudować layout w Blade, żeby nie powtarzać nagłówka i stopki?

Layout (layout) to szablon-rama, który definiuje wspólną strukturę strony i zostawia w niej „dziury” na treść konkretnych widoków. Zaczynasz od pliku resources/views/layouts/app.blade.php:

<!DOCTYPE html>
<html lang="pl">
<head>
    <title>@yield('title', 'devmentor')</title>
    @stack('styles')
</head>
<body>
    @include('partials.nav')
    <main>
        @yield('content')
    </main>
    @stack('scripts')
</body>
</html>

@yield('content') to miejsce na treść widoku, a drugi argument w @yield('title', 'devmentor') to wartość domyślna. Widok, który korzysta z layoutu, deklaruje to dyrektywą @extends i wypełnia sekcje:

@extends('layouts.app')

@section('title', 'Lista postów')

@push('scripts')
    <script src="/posts.js"></script>
@endpush

@section('content')
    <h1>{{ $title }}</h1>
    ...
@endsection

Po wyrenderowaniu dostaliśmy pełną stronę: tytuł Lista postów w <head>, nagłówek Posty w <main> oraz <script src="/posts.js"> na samym końcu <body>. Trzy mechanizmy robią tu różne rzeczy:

MechanizmCo robiKiedy użyć
@yield / @sectionwstawia jedną sekcję do layoutu (nadpisuje)główna treść, tytuł strony
@stack / @pushzbiera fragmenty z wielu miejsc (dopisuje)skrypty i style potrzebne tylko niektórym widokom
@includewstawia osobny plik w miejscu wywołaniamenu, stopka, powtarzalny fragment

Różnica między @section a @push jest częstym źródłem pomyłek. Sekcję wypełnia jedna wartość, a stos pozwala, żeby każdy widok i każdy komponent dopisał do niego swój fragment (np. własny skrypt), nie nadpisując cudzych.

Jak przekazać zmienną do layoutu i do @include?

Layout nie ma własnych danych. Dostaje dokładnie te zmienne, które przekazałeś widokowi, który go rozszerza. Jeśli kontroler wywoła view('posts', ['siteName' => 'devmentor']), zmienna $siteName będzie dostępna zarówno w widoku, jak i w layoucie. Dla danych potrzebnych na każdej stronie (np. zalogowany użytkownik) stosuje się wtedy view composers lub View::share(), ale to temat na dalszy etap nauki.

Dyrektywa @include przekazuje do dołączanego pliku wszystkie zmienne bieżącego widoku, a dodatkowo możesz podać własne jako drugi argument. Plik partials/nav.blade.php zawiera wiersz Menu: {{ $siteName ?? 'brak nazwy' }}. Wywołanie:

@include('partials.nav', ['siteName' => 'Z include'])

wypisało Menu: Z include, a gdy nie przekazaliśmy siteName, dostaliśmy Menu: brak nazwy. Są też warianty bezpieczne dla plików, które mogą nie istnieć: @includeIf('partials.nie-ma') po cichu nic nie wypisał, a @includeWhen(false, 'partials.nav') nie wstawił fragmentu, bo warunek był fałszywy. Zwykłe @include dla nieistniejącego pliku zgłasza błąd.

Czym są komponenty Blade i czym różnią się od @include?

Komponent (component) to widok wielokrotnego użytku, który przyjmuje parametry i fragmenty treści. Zwykły @include wstawia kawałek HTML-a, a komponent zachowuje się jak własny znacznik HTML: <x-alert>. Plik resources/views/components/alert.blade.php staje się znacznikiem <x-alert>.

@props(['type' => 'info'])
<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
    @isset($title)<strong>{{ $title }}</strong>@endisset
    {{ $slot }}
</div>

Dyrektywa @props deklaruje parametry (props) z wartościami domyślnymi. $slot to treść umieszczona między znacznikami, a $attributes zawiera wszystkie atrybuty, których komponent nie zadeklarował jako props. Użycie:

<x-alert type="danger" class="mt-4" id="a1">
    <x-slot:title>Uwaga</x-slot:title>
    Coś poszło nie tak: {{ $msg }}
</x-alert>
<x-alert>Domyślny typ</x-alert>

Wyrenderowany HTML (dla $msg = '<b>x</b>'):

<div class="alert alert-danger mt-4" id="a1">
 <strong>Uwaga</strong> Coś poszło nie tak: &lt;b&gt;x&lt;/b&gt;
</div>
<div class="alert alert-info">
 Domyślny typ
</div>

Widać tu cztery rzeczy naraz. Po pierwsze, type="danger" trafił do zmiennej $type, a bez niego użyty został info. Po drugie, atrybuty class="mt-4" i id="a1" zostały scalone z klasami komponentu dzięki $attributes->merge(...). Po trzecie, nazwany slot <x-slot:title> trafił do zmiennej $title, a reszta treści do $slot. Po czwarte, {{ $msg }} nadal escapuje dane, także wewnątrz komponentu.

Skoro zarówno @include, jak i komponent wstawiają fragment, to kiedy co wybrać?

Sytuacja@includeKomponent <x-...>
Stały fragment bez parametrów (stopka, menu)tak, wystarczyniepotrzebny narzut
Fragment z wariantami (typ alertu, rozmiar przycisku)niewygodnetak, przez props
Fragment z własną treścią w środku (karta, modal)trudnetak, przez slot
Scalanie atrybutów (class, id, data-*)brak$attributes

Reguła praktyczna: zaczynaj od @include, a gdy zauważysz, że dopisujesz do niego kolejne zmienne i warunki, zamień go na komponent. Komponenty mają też wariant klasowy, tworzony poleceniem php artisan make:component, który pozwala dodać logikę PHP do komponentu. Tego wariantu w tym artykule nie sprawdzaliśmy; pliki anonimowe, takie jak alert.blade.php powyżej, w zupełności wystarczą na początek.

Jak działa cache szablonów Blade i kiedy go czyścić?

Laravel kompiluje szablony przy pierwszym użyciu. Po wyrenderowaniu naszych widoków w storage/framework/views pojawiło się 7 skompilowanych plików PHP, po jednym na każdy użyty szablon. W środowisku lokalnym zmiana pliku .blade.php odświeża jego wersję automatycznie. Na serwerze produkcyjnym możesz skompilować wszystkie szablony z góry:

php artisan view:cache
php artisan view:clear

Pierwsze polecenie kompiluje widoki (Laravel odpowiada Blade templates cached successfully.), drugie czyści skompilowane pliki. Jeśli po wdrożeniu widzisz starą wersję strony, view:clear jest pierwszym, co warto uruchomić.

Jakie błędy w Blade popełniają początkujący?

BłądSkutekCo zrobić
{!! $dane !!} dla danych od użytkownikaluka XSSużywaj {{ }}, a surowy HTML tylko z zaufanego źródła
Zapytania do bazy w szablonie (@php + User::all())logika w widoku, trudne testy, N+1przygotuj dane w kontrolerze i przekaż je do widoku
Undefined variablekontroler nie przekazał zmiennejsprawdź tablicę w view() albo użyj ??
Skopiowany ten sam HTML w wielu widokachzmiana wymaga edycji wielu plikówwydziel layout, @include lub komponent
@section tam, gdzie potrzebny @pushskrypt jednego widoku nadpisuje skrypt innegodo dopisywania używaj @stack / @push
Brak @csrf w formularzu POSTodpowiedź 419 (wygasła strona)dodaj @csrf wewnątrz <form>; o mechanizmie piszemy w artykule o CSRF

Ostatni wiersz wymaga komentarza: @csrf generuje ukryte pole z tokenem, bez którego Laravel odrzuci formularz. Jeśli piszesz własną walidację, zobacz też walidację formularza w PHP, a w Laravelu zrobisz to samo krócej, przez Form Requesty.

Jaki błąd z Blade popełniają juniorzy najczęściej?

Najczęstsza pomyłka juniorów to {!! !!} użyte po to, żeby naprawić wyświetlanie znaczników. Zamiast tego zmień dane w kontrolerze, na przykład przekaż osobno nagłówek i listę, albo użyj komponentu. {!! !!} zostaw dla HTML-a, który sam wygenerowałeś.

Gdzie nauczyć się Blade w praktyce?

Blade najłatwiej zrozumieć, budując prawdziwy projekt: layout, kilka widoków, formularz i listę danych z bazy. Taką drogę przechodzisz w kursie PHP, gdzie moduł o Laravelu obejmuje routing, widoki i system szablonów Blade, a następnie Eloquent, uwierzytelnianie i wdrożenie. Mentor sprawdza twój kod i wskazuje miejsca, w których logika wylądowała w widoku zamiast w kontrolerze.

Konsultacja i warsztaty devmentor.pl

Masz pytania?

Partnerzy

Firmy, z którymi współpracujemy

To partnerzy odpowiedzialni za staże po naszych kursach. Organizują je dla najlepszych absolwentów.

Zobacz wszystkich partnerów →
  • VR Medical
  • Sophona
  • Robot Force