Blade: Layouts & Components

The previous lesson covered Blade's basic directives — {{ }} for escaped output, @if/@foreach for control structures, and the general idea that Blade compiles down to plain PHP. This lesson builds on that foundation with the features that make Blade genuinely useful for building real applications: layouts so you don't repeat your `<html>`, header, and footer on every single page, and components so you don't repeat little pieces of UI (alerts, cards, form fields) either. Both are about one idea — Don't Repeat Yourself — applied to your views instead of your PHP logic.

Template Inheritance: @extends and @section

Instead of every view being a complete, self-contained HTML document, you define one master layout that holds the shared skeleton (the <head>, navigation, footer), with a placeholder marking where page-specific content should go. Individual views then extend that layout and only supply the content for the placeholder. The placeholder is declared in the layout with @yield('name'), and a child view fills it using @section('name') ... @endsection.

Example: A Master Layout

<!-- resources/views/layouts/app.blade.php -->
<!DOCTYPE html>
<html>
<head>
    <title>@yield('title', 'My App')</title>
</head>
<body>

    <nav>Site navigation goes here</nav>

    <main>
        @yield('content')
    </main>

    <footer>&copy; 2026 My App</footer>
</body>
</html>

Example: A Child View Extending the Layout

<!-- resources/views/articles/show.blade.php -->
@extends('layouts.app')

@section('title', $article->title)

@section('content')
    <h1>{{ $article->title }}</h1>
    <p>{{ $article->body }}</p>
@endsection

Notice that show.blade.php no longer contains any <html> or <body> tags at all — it only supplies the two sections the layout expects. When this view is rendered, Blade takes layouts/app.blade.php, and wherever it finds @yield('content'), it drops in whatever was defined inside @section('content') ... @endsection in the child view. The optional second argument to @yield('title', 'My App') is a default value used if the child view never defines that section — handy so every page doesn't have to set a title.

Convention: Layout files are almost always kept in resources/views/layouts/, and the dot notation in @extends('layouts.app') maps directly to that folder path — dots become slashes, and .blade.php is assumed.

Blade Components: Reusable UI Pieces

Layouts solve repetition at the whole-page level. Components solve it at the smaller, reusable-widget level — an alert box, a card, a form input with a label and error message, a modal — anything you'd otherwise copy-paste across several views. A component is generated with Artisan and consists of two halves: a small PHP class (optional, for logic) and a Blade view (the markup).

Example: Generating and Defining a Component

# From your project's terminal
php artisan make:component Alert

This creates two files:

  • app/View/Components/Alert.php — the component's class
  • resources/views/components/alert.blade.php — the component's markup
<?php
// app/View/Components/Alert.php
namespace App\View\Components;

use Illuminate\View\Component;

class Alert extends Component
{
    public $type;

    public function __construct($type = 'info')
    {
        $this->type = $type;
    }

    public function render()
    {
        return view('components.alert');
    }
}
<!-- resources/views/components/alert.blade.php -->
<div class="alert alert-{{ $type }}">
    {{ $slot }}
</div>

Using the component anywhere in the application looks like a plain HTML tag:

<x-alert type="error">Something went wrong</x-alert>

Passing Data and Using the Slot

The constructor argument $type automatically becomes available to the component's view as a variable — that's what makes type="error" in the tag turn into $type equal to "error" inside alert.blade.php. Any attribute you put on the <x-alert> tag that matches a constructor parameter is passed through automatically; Laravel wires this up for you.

The content placed between the opening and closing tags — Something went wrong in the example above — is captured automatically into a special variable called $slot. Printing {{ $slot }} inside the component's view is what renders that inner content. This is what makes components genuinely flexible: the caller controls the inner content, the component controls the wrapping markup and styling.

Note: A component can define more than one slot using named slots (via <x-slot name="...">) for more advanced layouts, such as a card component with a separate header and body — but the single default $slot covers the large majority of everyday cases.

Anonymous Components

Not every component needs a PHP class behind it. If a component is purely markup with no constructor logic to run, you can skip the class entirely and just create a Blade file directly inside resources/views/components/. Laravel automatically treats any file placed there as a usable <x-*> component, even without a matching class.

Example: An Anonymous Alert Component

<!-- resources/views/components/alert.blade.php (no class needed) -->
@props(['type' => 'info'])

<div class="alert alert-{{ $type }}">
    {{ $slot }}
</div>

The @props directive at the top declares which attributes count as component "props" (with optional defaults) versus which should just be passed through as plain HTML attributes. It's the anonymous-component equivalent of the constructor arguments in the class-based version, and it's used exactly the same way from the calling view: <x-alert type="error">Something went wrong</x-alert>. For components that are simple presentation with no PHP logic to run, anonymous components mean one less file to maintain.

Including Sub-Views with @include

Before components existed, and still very commonly today for simpler cases, Blade offers @include to pull in another view's markup inline. It's a more basic tool than a component — no props system, no slots — it simply drops the included view's compiled output at that point, with access to whatever variables are already in scope.

<!-- resources/views/layouts/app.blade.php -->
<body>
    @include('partials.header')

    @yield('content')

    @include('partials.footer')
</body>

Reach for @include for small, static fragments (a header, a footer, a repeated sidebar block); reach for a full component when the piece of UI needs its own logic, props, or is reused with varying data across many different pages.

Stacks: @push and @stack

Here's a genuinely useful, less obvious feature: sometimes one specific child view needs to add its own <script> or <style> tag, but only the layout controls where the closing </body> tag is. Copy-pasting the whole layout just to add one script tag would defeat the purpose of having a shared layout at all. Stacks solve this: the layout declares a named "stack" as a slot for content, and any child view can @push content onto that named stack from anywhere, even though the actual output location is fixed by the layout.

Example: Page-Specific Scripts via a Stack

<!-- resources/views/layouts/app.blade.php -->
<body>
    @yield('content')

    <script src="/js/app.js"></script>
    @stack('scripts')
</body>
<!-- resources/views/articles/show.blade.php -->
@extends('layouts.app')

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

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

Only the show.blade.php view for an article page pushes article-comments.js, but the actual <script> tag ends up printed wherever @stack('scripts') was declared in the layout — right after the shared app.js, in the right place, without touching the layout file at all. Views that don't push anything to that stack simply render nothing extra there.

Quick recap of what to reach for:
FeatureUse it for
@extends / @yieldSharing the overall page skeleton across many views
Class-based componentReusable UI that needs PHP logic behind it
Anonymous componentReusable UI that's markup-only, no logic needed
@includeSimple, static fragments reused across views
@push / @stackLetting a specific view inject content into a fixed spot in the layout
Next up: Every one of these views can eventually sit behind protected routes. Head to Middleware to learn how Laravel filters requests before they ever reach your controllers.