π§© 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:
| Approach | Best for | Mental 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:
@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>© {{ 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.
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">×</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:
- Create
resources/views/components/alert.blade.php. - Accept two props with
@props:type(default'info') anddismissible(defaultfalse). - Render the message (the
$slot) inside adivwhose class includesalert-{type}. - Merge any extra attributes onto that root
div. - When
dismissibleis true, render a close button. - Use it once as a
successalert and once as a dismissibledangeralert.
π‘ 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">×</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:
@extendsin the child,@yield/@section/@showin the layout. @parentkeeps 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@propsand 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.