Skip to main content

🧩 Layout Inheritance and Components

A website's header, nav, and footer shouldn't be copy-pasted onto every page. Blade offers two ways to share structure: classic layout inheritance and the modern component system. This lesson shows both, when to use each, and how to build a small library of reusable UI pieces.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Build a master layout and extend it with @extends, @yield, and @section
  • Use stacks (@stack/@push) to collect page-specific scripts and styles
  • Create anonymous and class-based components and pass data with @props
  • Use default and named slots, and merge HTML attributes onto a component's root element
  • Choose sensibly between layout inheritance and components for a given piece of UI

Estimated Time: 40–50 minutes  β€’  Difficulty: Intermediate

Hands-on: Build a reusable <x-alert> component with a type and dismiss button.

In This Lesson

Two Ways to Share Structure

Every page on your site probably shares a skeleton: the same <head>, a header, navigation, a footer. Writing that skeleton once and reusing it is the essence of the DRY principle β€” Don't Repeat Yourself. Blade gives you two complementary tools:

ApproachBest forMental model
Layout inheritance (@extends)The overall page shell each view "fills in"A master blueprint the whole building follows
Components (<x-…>)Small, reusable UI pieces (alerts, cards, inputs)Prefab appliances you plug in anywhere

πŸ’‘ Which should I use?

Reach for a layout for the page frame β€” the thing pages extend. Reach for components for the repeated widgets that appear within pages. Modern Laravel leans on components heavily, and there's even a component-based way to define layouts, which we'll note at the end.

Creating a Master Layout

A master layout is an ordinary Blade file (conventionally in resources/views/layouts) with placeholders that child views will fill. Here's the anatomy:

Layout inheritance structure A master layout defines fixed header and footer plus a yield placeholder; a child view extends it and fills the content section. layouts/app.blade.php header + nav (fixed) @yield('content') placeholder footer (fixed) pages/home.blade.php @extends('layouts.app') @section('content') …this page's HTML… fills
Figure 1 β€” The layout owns the fixed frame; each child view slots its own content into the @yield placeholder.
{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@yield('title', 'My App')</title>
    @vite(['resources/css/app.css', 'resources/js/app.js'])
    @stack('styles')
</head>
<body>
    <header class="site-header">
        @include('partials.navigation')
    </header>

    <main class="container">
        @yield('content')
    </main>

    <aside class="sidebar">
        @section('sidebar')
            <p>Default sidebar content.</p>
        @show
    </aside>

    <footer>&copy; {{ date('Y') }} My App</footer>

    @stack('scripts')
</body>
</html>

πŸ“– The four building blocks

@yield('name', 'default'): an empty slot a child fills; the optional second argument is fallback content.

@section / @show: defines default content right here that a child may override or extend.

@include: pulls in a partial view (the nav here).

@stack: a collection point child views push scripts or styles into.

@yield is an empty box waiting to be filled; @section...@show is a box that already has something in it, which a child can add to or swap out.

Extending a Layout

A child view declares its parent with @extends, then supplies content for each section.

{{-- resources/views/pages/home.blade.php --}}
@extends('layouts.app')

@section('title', 'Home')

@section('content')
    <h1>Welcome</h1>
    <p>This is the home page.</p>
@endsection

@section('sidebar')
    @parent  {{-- keep the layout's default sidebar... --}}
    <h3>Quick Links</h3>
    <ul><li><a href="#">About</a></li></ul>
@endsection

@push('scripts')
    <script src="{{ asset('js/home.js') }}"></script>
@endpush

βœ… The @parent trick

Inside a @section that also exists in the layout, @parent injects the layout's default content, then lets you append your own. It's "keep what's already here, and add this" β€” like renovating a room while preserving its original fittings. Note the short form @section('title', 'Home'): when a section is just a string, you can pass it inline with no @endsection.

Stacks for Scripts & Styles

A page might need one extra stylesheet or a chart library that other pages don't. Rather than dumping everything into the layout, child views push onto a named stack, and the layout renders it in one place.

{{-- In the layout --}}
<head>
    @vite('resources/css/app.css')
    @stack('styles')
</head>
<body>
    {{-- ... --}}
    @stack('scripts')
</body>

{{-- In a child view --}}
@push('styles')
    <link rel="stylesheet" href="{{ asset('css/chart.css') }}">
@endpush

@push('scripts')
    <script src="{{ asset('js/chart.js') }}"></script>
@endpush

You can push to the same stack from several places and everything collects in order. Use @prepend to add to the front instead β€” handy for a dependency that must load before everything else. Stacks are like a shared inbox: different parts of the app drop items in, and the layout assembles them at the right spot.

Meet Blade Components

Layouts solve the "page frame" problem. Components solve the "reusable widget" problem β€” a self-contained piece of UI you invoke like a custom HTML tag: <x-alert />. Components take explicit inputs, encapsulate their own markup, and update everywhere at once when you change them.

graph TD A[Blade Components] --> B[Anonymous] A --> C[Class-based] B --> D[resources/views/components/*.blade.php] C --> E[App\View\Components + a view] F[Invoke with] --> G["<x-alert />"] F --> H["<x-form.input /> for subfolders"]

There are two flavours: anonymous components (just a Blade file) and class-based components (a PHP class paired with a view, for when you need real logic). Start anonymous; graduate to class-based only when the logic earns it.

Anonymous Components

Create a file under resources/views/components and it becomes usable immediately β€” no registration, no class. Declare its inputs with @props.

{{-- resources/views/components/alert.blade.php --}}
@props(['type' => 'info', 'title' => null])

<div {{ $attributes->merge(['class' => "alert alert-{$type}"]) }}>
    @if ($title)
        <p class="alert-title">{{ $title }}</p>
    @endif
    <div class="alert-body">{{ $slot }}</div>
</div>

The @props directive lists accepted attributes and their defaults; $slot is whatever sits between the opening and closing tags. Use it like a native element:

<x-alert type="danger" title="Error">
    Something went wrong β€” please try again.
</x-alert>

<x-alert title="Heads up">
    Your profile was updated.
</x-alert>

A component in a subfolder (components/form/input.blade.php) is addressed with a dot: <x-form.input />.

Class-based Components

When a component needs computed values, dependency injection, or helper methods, generate a class-based one with Artisan:

php artisan make:component Alert

# Creates:
#   app/View/Components/Alert.php
#   resources/views/components/alert.blade.php

Public properties and public methods on the class are automatically available inside the view. In Laravel 11 the constructor uses typed, promoted properties:

<?php
// app/View/Components/Alert.php
namespace App\View\Components;

use Illuminate\View\Component;
use Illuminate\View\View;

class Alert extends Component
{
    public function __construct(
        public string $type = 'info',
        public ?string $title = null,
    ) {}

    // Available in the view as $isImportant()
    public function isImportant(): bool
    {
        return in_array($this->type, ['danger', 'warning']);
    }

    public function render(): View
    {
        return view('components.alert');
    }
}
{{-- resources/views/components/alert.blade.php --}}
<div {{ $attributes->merge(['class' => "alert alert-{$type}"]) }}>
    @if ($title)
        <p class="alert-title">{{ $title }}</p>
    @endif

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

    @if ($isImportant())
        <button class="close-btn" aria-label="Dismiss">&times;</button>
    @endif
</div>

πŸ’‘ Anonymous vs. class-based

Use an anonymous component when it's pure markup with a few props. Reach for a class-based component when you need computed helpers (like isImportant()), want to inject a service, or the constructor should transform its inputs.

Slots & Attributes

Named slots

Besides the default $slot, a component can accept several named slots β€” think header, body, footer regions.

{{-- resources/views/components/modal.blade.php --}}
<div class="modal">
    <div class="modal-header">{{ $title }}</div>
    <div class="modal-body">{{ $slot }}</div>

    @if (isset($footer))
        <div class="modal-footer">{{ $footer }}</div>
    @endif
</div>

{{-- Using it --}}
<x-modal>
    <x-slot:title>Confirm deletion</x-slot:title>

    Are you sure? This can't be undone.

    <x-slot:footer>
        <button class="btn">Cancel</button>
        <button class="btn btn-danger">Delete</button>
    </x-slot:footer>
</x-modal>

Named slots are labelled compartments β€” a title goes in the title area, footer buttons in the footer β€” just like the placeholders on a slide template.

Attribute merging

Any HTML attribute you pass to a component that isn't a declared prop lands in the $attributes bag. merge() combines your extra classes with the component's own:

{{-- Component root --}}
<div {{ $attributes->merge(['class' => 'alert alert-info']) }}>
    {{ $slot }}
</div>

{{-- Usage --}}
<x-alert class="mt-4" id="signup-alert">Welcome!</x-alert>

{{-- Renders as --}}
<div class="alert alert-info mt-4" id="signup-alert">Welcome!</div>

This is what makes components feel like real HTML elements: you can decorate them with class, id, data-*, and ARIA attributes, and they merge in cleanly without you predefining each one.

Worked Example: A Card Component

Let's build a flexible <x-card> β€” an anonymous component with an optional title, a body slot, and an optional footer slot β€” then use it in a page that extends our layout.

{{-- resources/views/components/card.blade.php --}}
@props(['title' => null])

<div {{ $attributes->merge(['class' => 'card']) }}>
    @if ($title)
        <div class="card-header"><h3>{{ $title }}</h3></div>
    @endif

    <div class="card-body">{{ $slot }}</div>

    @isset($footer)
        <div class="card-footer">{{ $footer }}</div>
    @endisset
</div>
{{-- resources/views/posts/index.blade.php --}}
@extends('layouts.app')

@section('title', 'Latest Posts')

@section('content')
    <h1>Latest Posts</h1>

    @forelse ($posts as $post)
        <x-card :title="$post->title" class="mb-3">
            <p>{{ $post->excerpt }}</p>

            <x-slot:footer>
                <a href="{{ route('posts.show', $post) }}">Read more</a>
            </x-slot:footer>
        </x-card>
    @empty
        <p>No posts published yet.</p>
    @endforelse

    {{ $posts->links() }}
@endsection

Everything working together

  • The page extends the master layout and fills @section('content')
  • An anonymous component renders each post card
  • :title="$post->title" passes a PHP value as a prop (note the : prefix)
  • A named slot supplies the footer link
  • class="mb-3" merges onto the card's root

πŸ“– Component-based layouts

Laravel also lets you express the layout itself as a component β€” <x-app-layout>...</x-app-layout> wrapping a page's content in its default slot. Laravel's own starter kits use this style. It's the same idea as @extends, just written with component syntax, so once you're fluent with components the two approaches converge.

Hands-on Exercise

πŸ‹οΈ Build a Dismissible Alert Component

Objective: Create an anonymous <x-alert> component and use it twice with different types.

Instructions:

  1. Create resources/views/components/alert.blade.php.
  2. Accept two props with @props: type (default 'info') and dismissible (default false).
  3. Render the message (the $slot) inside a div whose class includes alert-{type}.
  4. Merge any extra attributes onto that root div.
  5. When dismissible is true, render a close button.
  6. Use it once as a success alert and once as a dismissible danger alert.
πŸ’‘ Hint

Booleans pass as bare attributes: <x-alert dismissible> sets it true. Use $attributes->merge(['class' => "alert alert-{$type}"]) so callers can still add their own classes.

βœ… Sample solution
{{-- resources/views/components/alert.blade.php --}}
@props(['type' => 'info', 'dismissible' => false])

<div {{ $attributes->merge(['class' => "alert alert-{$type}"]) }} role="alert">
    <div class="alert-body">{{ $slot }}</div>

    @if ($dismissible)
        <button type="button" class="alert-close" aria-label="Dismiss">&times;</button>
    @endif
</div>
{{-- Using it --}}
<x-alert type="success">
    Your changes have been saved.
</x-alert>

<x-alert type="danger" dismissible class="mt-3">
    We couldn't process your payment.
</x-alert>

🎯 Quick Quiz

Question 1: In a master layout, what is the difference between @yield and @section...@show?

Question 2: How do you pass a PHP variable as a prop to a component?

Question 3: A caller writes <x-alert class="mt-4">. What lets that extra class combine with the component's own alert class?

Summary & Quiz

πŸŽ‰ Key Takeaways

  • Layout inheritance defines a page frame: @extends in the child, @yield/@section/@show in the layout.
  • @parent keeps a layout's default section content while a child adds to it.
  • Stacks (@stack/@push/@prepend) collect page-specific scripts and styles into one place.
  • Components are reusable widgets invoked as <x-name>; declare inputs with @props and use : to pass PHP values.
  • Anonymous components are pure markup; class-based ones add logic, helpers, and injection.
  • Named slots place content into regions; $attributes->merge() blends caller attributes onto the root.

πŸ“š Further Reading

πŸš€ What's Next?

Your views are in great shape. Next we drop below the presentation layer into the database: defining Eloquent models that map PHP objects to database tables and power the data your Blade views display.

πŸŽ‰ Excellent!

You can now compose whole, DRY interfaces. On to the data layer.