Blade Layout Inheritance and Components

Module 19: PHP Backend - Laravel

Introduction to Template Inheritance

Template inheritance is one of Blade's most powerful features, allowing you to define a master layout that contains all the common elements of your site (header, footer, navigation, etc.) and then extend this layout in child templates.

This approach is similar to architectural blueprints - you create a master plan for the building's structure, and then individual room plans inherit this overall structure while customizing their specific contents.

graph TD A[Master Layout] --> B[Section Placeholders] A --> C[Common Elements] C --> C1[Header] C --> C2[Navigation] C --> C3[Footer] D[Child View] --> E[Extends Master] D --> F[Fills Sections] F --> F1[Content] F --> F2[Sidebar] F --> F3[Scripts] A --> D

Template inheritance helps you maintain consistency across your application while eliminating repetition - a cornerstone of the DRY (Don't Repeat Yourself) principle.

Creating a Master Layout

Let's start by creating a master layout template:


<!-- 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', 'Default Title') - My App</title>
    
    <!-- Stylesheets -->
    <link rel="stylesheet" href="{{ asset('css/app.css') }}">
    @yield('styles')
</head>
<body>
    <header class="site-header">
        <div class="container">
            <h1>My Application</h1>
            @include('partials.navigation')
        </div>
    </header>
    
    <main class="site-content">
        <div class="container">
            @yield('content')
        </div>
    </main>
    
    <aside class="sidebar">
        @section('sidebar')
            <div class="default-sidebar-content">
                <h3>About My App</h3>
                <p>This is the default sidebar content.</p>
            </div>
        @show
    </aside>
    
    <footer class="site-footer">
        <div class="container">
            <p>&copy; {{ date('Y') }} My Application</p>
        </div>
    </footer>
    
    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}"></script>
    @stack('scripts')
</body>
</html>
            

This master layout includes several important Blade inheritance features:

Think of @yield as an empty container waiting to be filled, while @section/@show provides default content that can be modified. The difference is similar to giving someone an empty box versus a box with some items that they can add to or replace.

Extending Layouts

Child views extend the master layout and fill in the defined sections:


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

@section('title', 'Home Page')

@section('content')
    <h2>Welcome to the Home Page</h2>
    <p>This is the main content of the home page.</p>
    
    <div class="featured-content">
        <h3>Featured Items</h3>
        <div class="featured-grid">
            @foreach ($featuredItems as $item)
                <div class="featured-item">
                    <h4>{{ $item->title }}</h4>
                    <p>{{ $item->description }}</p>
                </div>
            @endforeach
        </div>
    </div>
@endsection

@section('sidebar')
    @parent
    <div class="home-sidebar-addition">
        <h3>Quick Links</h3>
        <ul>
            <li><a href="#">Link 1</a></li>
            <li><a href="#">Link 2</a></li>
            <li><a href="#">Link 3</a></li>
        </ul>
    </div>
@endsection

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

Let's examine what's happening in this child view:

The @parent directive within a section is particularly powerful - it allows you to include the parent's content and then add your own. It's like saying "keep what's already there, and add this too," similar to how you might renovate a room while keeping some of the original features.

Organizing Views with Subviews

For better organization, you can break your layouts into smaller, more manageable pieces using includes:

Partial Views (Includes)


<!-- resources/views/partials/navigation.blade.php -->
<nav class="main-navigation">
    <ul>
        <li><a href="{{ route('home') }}" class="{{ request()->routeIs('home') ? 'active' : '' }}">Home</a></li>
        <li><a href="{{ route('about') }}" class="{{ request()->routeIs('about') ? 'active' : '' }}">About</a></li>
        <li><a href="{{ route('services') }}" class="{{ request()->routeIs('services') ? 'active' : '' }}">Services</a></li>
        <li><a href="{{ route('contact') }}" class="{{ request()->routeIs('contact') ? 'active' : '' }}">Contact</a></li>
    </ul>
</nav>

<!-- In the main layout -->
@include('partials.navigation')
            

You can also pass variables to included views:


<!-- Passing variables to an include -->
@include('partials.user-profile', ['user' => $user, 'showEdit' => true])

<!-- In partials/user-profile.blade.php -->
<div class="user-profile">
    <img src="{{ $user->avatar }}" alt="{{ $user->name }}">
    <h2>{{ $user->name }}</h2>
    @if($showEdit)
        <a href="{{ route('users.edit', $user) }}" class="edit-btn">Edit Profile</a>
    @endif
</div>
            

Using partial views is like using prefabricated components in construction - they're ready-made pieces that can be assembled together to create the complete structure, saving time and ensuring consistency.

Using Stacks for Scripts and Styles

Stacks are a powerful way to collect content from child views and place it in a specific location in the parent layout. They're especially useful for JavaScript and CSS:


<!-- In the master layout -->
<head>
    <!-- Core Styles -->
    <link rel="stylesheet" href="{{ asset('css/app.css') }}">
    @stack('styles')
</head>
<body>
    <!-- Content... -->
    
    <!-- Core Scripts -->
    <script src="{{ asset('js/app.js') }}"></script>
    @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>
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            initializeCharts();
        });
    </script>
@endpush
            

You can push multiple times to the same stack from different locations in your views, and all the content will be collected in the order it was pushed.

You can also prepend to a stack to add content to the beginning instead of the end:


@prepend('scripts')
    <script src="{{ asset('js/vendor/jquery.js') }}"></script>
@endprepend
            

Stacks are like collection points where different parts of your application can contribute pieces that will be assembled in the final output - similar to how different departments might contribute sections to a company report that are then compiled in a specific order.

Introduction to Blade Components

While template inheritance is powerful, Blade components offer an even more flexible and reusable approach to view composition. Components are self-contained, reusable view fragments with their own logic.

Think of components as specialized, prefabricated modules - like kitchen appliances that perform specific functions and can be easily installed in any kitchen. They have their own internal logic but present a clean, simple interface to the outside world.

graph TD A[Blade Components] --> B[Class-based] A --> C[Anonymous] B --> D[App\View\Components] C --> E[resources/views/components] F[Component Usage] --> G[<x-component-name>] F --> H[@component]

Laravel offers two types of components:

  1. Class-based components - PHP classes paired with a view, offering more control and logic
  2. Anonymous components - View-only components without an associated class

Anonymous Components

Anonymous components are the simplest type - they're just Blade templates stored in the resources/views/components directory:


<!-- resources/views/components/alert.blade.php -->
<div class="alert alert-{{ $type ?? 'info' }}">
    <div class="alert-title">{{ $title }}</div>
    
    <div class="alert-body">
        {{ $slot }}
    </div>
</div>
            

This component accepts:

To use this component in your views:


<x-alert type="danger" title="Error">
    <p>Something went wrong! Please try again.</p>
</x-alert>

<x-alert title="Information">
    <p>Your profile has been updated.</p>
</x-alert>
            

Anonymous components are rendered directly, with attributes becoming available as variables. The content between the opening and closing tags is passed to the component as the $slot variable.

This approach is similar to how HTML elements work natively - you use an element tag, provide attributes, and place content between the tags.

Class-based Components

For components that need more logic, Laravel offers class-based components that pair a PHP class with a Blade view:

Creating a Class Component


# Generate a new component
php artisan make:component Alert

# This creates:
# 1. app/View/Components/Alert.php
# 2. resources/views/components/alert.blade.php
            

Component Class


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

use Illuminate\View\Component;

class Alert extends Component
{
    public $type;
    public $title;
    
    public function __construct($type = 'info', $title = null)
    {
        $this->type = $type;
        $this->title = $title;
    }
    
    public function render()
    {
        return view('components.alert');
    }
    
    // Helper methods available in the component view
    public function isImportant()
    {
        return in_array($this->type, ['danger', 'warning']);
    }
}
            

Component View


<!-- resources/views/components/alert.blade.php -->
<div class="alert alert-{{ $type }} {{ $isImportant() ? 'alert-important' : '' }}">
    @if($title)
        <div class="alert-title">{{ $title }}</div>
    @endif
    
    <div class="alert-body">
        {{ $slot }}
    </div>
    
    @if($isImportant())
        <button class="close-btn">×</button>
    @endif
</div>
            

Using Class Components


<x-alert type="danger" title="Error Detected">
    <p>Something went wrong! Please try again.</p>
</x-alert>
            

The key advantages of class components are:

Class components are like smart appliances with built-in processors - they don't just display information but can process it, make decisions, and adapt their behavior based on various inputs.

Component Slots

Slots allow you to pass content blocks to your components. You've already seen the default $slot, but components can also have named slots:

Component with Named Slots


<!-- resources/views/components/modal.blade.php -->
<div class="modal" x-data="{ open: false }" x-show="open" @keydown.escape.window="open = false">
    <div class="modal-header">
        <h3>{{ $title }}</h3>
        <button @click="open = false">×</button>
    </div>
    
    <div class="modal-body">
        {{ $slot }}
    </div>
    
    @if (isset($footer))
        <div class="modal-footer">
            {{ $footer }}
        </div>
    @endif
</div>
            

Using Named Slots


<x-modal title="Confirm Deletion">
    <p>Are you sure you want to delete this item? This action cannot be undone.</p>
    
    <x-slot name="footer">
        <button class="btn btn-secondary" @click="open = false">Cancel</button>
        <button class="btn btn-danger" @click="deleteItem()">Delete</button>
    </x-slot>
</x-modal>
            

Named slots are like designated areas within a component where you can place specific content, similar to how a presentation template might have specific areas for titles, body content, and footnotes.

Checking for Slot Content


@if ($slot->isEmpty())
    <p>No content provided.</p>
@else
    {{ $slot }}
@endif
            

This allows components to adapt based on whether specific content is provided.

Component Attributes

Components can receive and handle HTML attributes in a flexible way:

Attribute Merging


<!-- Component definition -->
<div {{ $attributes }}>
    {{ $slot }}
</div>

<!-- Usage -->
<x-alert class="mt-4" id="my-alert" data-dismiss="alert">
    Alert content
</x-alert>

<!-- Renders as -->
<div class="mt-4" id="my-alert" data-dismiss="alert">
    Alert content
</div>
            

Merging Classes with Existing Classes


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

<!-- Usage -->
<x-alert class="mt-4">
    Alert content
</x-alert>

<!-- Renders as -->
<div class="alert alert-info alert-dismissible mt-4">
    Alert content
</div>
            

Filtering Attributes


<!-- Component definition -->
<div {{ $attributes->merge(['class' => 'alert']) }}>
    <h4 {{ $attributes->only('title') }}>{{ $title }}</h4>
    <div {{ $attributes->except(['class', 'title']) }}>
        {{ $slot }}
    </div>
</div>
            

These attribute handling capabilities make components extremely flexible - they can accept any HTML attributes while still maintaining their core functionality, similar to how a good template system lets you customize styles and attributes without changing the underlying structure.

Component Organization and Namespacing

As your application grows, you might want to organize components into subdirectories:

Directory Structure


resources/views/components/
├── alert.blade.php
├── form/
│   ├── input.blade.php
│   ├── textarea.blade.php
│   └── select.blade.php
├── ui/
│   ├── button.blade.php
│   ├── card.blade.php
│   └── modal.blade.php
            

Using Namespaced Components


<!-- Components in subdirectories -->
<x-form.input type="text" name="title" label="Post Title" />
<x-ui.card title="Recent Posts">
    <!-- Card content -->
</x-ui.card>
            

For class-based components, you can define namespaces in your app/Providers/AppServiceProvider.php:


use Illuminate\Support\Facades\Blade;

public function boot()
{
    // Register 'admin' component namespace
    Blade::componentNamespace('App\\View\\Components\\Admin', 'admin');
}

// Then use components like:
<x-admin::dashboard />
            

This organization is like having a well-structured library where books are categorized by genre and topic - it makes finding and using components much easier as your collection grows.

Dynamic Components

Sometimes you need to render components dynamically based on runtime conditions:


<!-- Using a variable to determine which component to render -->
@php
    $componentName = $message->type === 'error' ? 'error-box' : 'info-box';
@endphp

<x-dynamic-component :component="$componentName" :message="$message" />
            

The :component attribute accepts a string that specifies which component to render. All other attributes are passed to the resolved component.

This approach is like having modular furniture pieces that can be swapped in and out based on your needs - the same space can hold different pieces depending on the situation.

Advanced Component Patterns

Form Input Components

Let's build a reusable form input component:


<!-- resources/views/components/form/input.blade.php -->
@props(['name', 'label' => null, 'type' => 'text', 'value' => ''])

<div class="form-group">
    @if($label)
        <label for="{{ $name }}">{{ $label }}</label>
    @endif
    
    <input 
        type="{{ $type }}" 
        name="{{ $name }}" 
        id="{{ $name }}" 
        value="{{ old($name, $value) }}" 
        {{ $attributes->merge(['class' => 'form-control ' . ($errors->has($name) ? 'is-invalid' : '')]) }}
    >
    
    @error($name)
        <div class="invalid-feedback">
            {{ $message }}
        </div>
    @enderror
</div>
            

Usage of this component:


<x-form.input 
    name="email" 
    label="Email Address" 
    type="email" 
    value="{{ $user->email }}" 
    required 
    autocomplete="email" 
/>
            

This component handles:

Card Component with Conditional Sections


<!-- resources/views/components/ui/card.blade.php -->
@props(['title' => null, 'footer' => null, 'headerClass' => '', 'bodyClass' => '', 'footerClass' => ''])

<div {{ $attributes->merge(['class' => 'card']) }}>
    @if($title || isset($header))
        <div class="card-header {{ $headerClass }}">
            @if(isset($header))
                {{ $header }}
            @else
                <h4>{{ $title }}</h4>
            @endif
        </div>
    @endif
    
    <div class="card-body {{ $bodyClass }}">
        {{ $slot }}
    </div>
    
    @if($footer || isset($footer))
        <div class="card-footer {{ $footerClass }}">
            @if(isset($footer))
                {{ $footer }}
            @else
                {{ $footer }}
            @endif
        </div>
    @endif
</div>
            

Usage with different configurations:


<!-- Basic usage -->
<x-ui.card title="Recent Posts">
    <p>Card content here...</p>
</x-ui.card>

<!-- With custom header -->
<x-ui.card>
    <x-slot name="header">
        <div class="d-flex justify-content-between align-items-center">
            <h4>Recent Posts</h4>
            <a href="#" class="btn btn-sm btn-primary">View All</a>
        </div>
    </x-slot>
    
    <p>Card content here...</p>
    
    <x-slot name="footer">
        <div class="text-right">
            <button class="btn btn-secondary">Load More</button>
        </div>
    </x-slot>
</x-ui.card>
            

These advanced component patterns create a consistent, reusable interface for common UI elements, significantly reducing repetition in your views while maintaining flexibility.

Real-World Example: E-commerce Product Page

Let's examine a comprehensive example that combines layouts, components, and includes to create a complete e-commerce product page:

Master Layout (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', 'E-Commerce Store')</title>
    <link rel="stylesheet" href="{{ asset('css/app.css') }}">
    @stack('styles')
</head>
<body>
    @include('partials.header')
    
    <main class="container py-4">
        @yield('content')
    </main>
    
    @include('partials.footer')
    
    <script src="{{ asset('js/app.js') }}"></script>
    @stack('scripts')
</body>
</html>
            

Product Detail Page (products/show.blade.php)


@extends('layouts.app')

@section('title', $product->name . ' - E-Commerce Store')

@section('content')
    <div class="product-page">
        <div class="row">
            <div class="col-md-6">
                <x-product.gallery :images="$product->images" />
            </div>
            
            <div class="col-md-6">
                <h1>{{ $product->name }}</h1>
                
                <x-product.price-display 
                    :regular-price="$product->regular_price" 
                    :sale-price="$product->sale_price"
                    :discount-percentage="$product->discount_percentage"
                />
                
                <div class="product-ratings mb-3">
                    <x-ui.star-rating :rating="$product->average_rating" />
                    <span class="ml-2">{{ $product->reviews_count }} reviews</span>
                </div>
                
                <div class="product-description mb-4">
                    {{ $product->short_description }}
                </div>
                
                @if($product->hasVariants())
                    <x-product.variant-selector :variants="$product->variants" />
                @endif
                
                <x-product.add-to-cart :product="$product" />
                
                <x-ui.accordion>
                    <x-ui.accordion-item title="Description">
                        {!! $product->description !!}
                    </x-ui.accordion-item>
                    
                    <x-ui.accordion-item title="Specifications">
                        @include('products.partials.specifications', ['specifications' => $product->specifications])
                    </x-ui.accordion-item>
                    
                    <x-ui.accordion-item title="Shipping & Returns">
                        @include('partials.shipping-returns')
                    </x-ui.accordion-item>
                </x-ui.accordion>
            </div>
        </div>
        
        <section class="related-products mt-5">
            <h2>Related Products</h2>
            <div class="row">
                @foreach($relatedProducts as $relatedProduct)
                    <div class="col-md-3">
                        <x-product.card :product="$relatedProduct" />
                    </div>
                @endforeach
            </div>
        </section>
        
        <section class="product-reviews mt-5">
            <h2>Customer Reviews</h2>
            
            @if($reviews->isNotEmpty())
                <div class="reviews-summary mb-4">
                    <x-product.reviews-summary :product="$product" />
                </div>
                
                <div class="reviews-list">
                    @foreach($reviews as $review)
                        <x-product.review-item :review="$review" />
                    @endforeach
                </div>
                
                {{ $reviews->links() }}
            @else
                <p>This product doesn't have any reviews yet. Be the first to leave a review!</p>
            @endif
            
            @auth
                <div class="leave-review mt-4">
                    <h3>Leave a Review</h3>
                    <x-product.review-form :product="$product" />
                </div>
            @else
                <p><a href="{{ route('login') }}">Log in</a> to leave a review.</p>
            @endauth
        </section>
    </div>
@endsection

@push('styles')
    <link rel="stylesheet" href="{{ asset('css/product-page.css') }}">
@endpush

@push('scripts')
    <script src="{{ asset('js/product-gallery.js') }}"></script>
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            new ProductGallery('.product-gallery');
        });
    </script>
@endpush
            

This example demonstrates:

The structure creates a modular, maintainable codebase where changes to components automatically propagate to all pages using them. For instance, updating the product.card component would instantly update all product cards across the site.

Best Practices

Layout Best Practices

Component Best Practices

Practice Activity

Master Layout Creation

Create a master layout for a blog application that includes:

  1. A header with navigation
  2. A main content area
  3. A sidebar with configurable content
  4. A footer with copyright information
  5. Stacks for styles and scripts

Then create two child views that extend this layout: a blog post list and a single post view.

Component Library

Create a small component library for a blog that includes:

  1. A post-card component for displaying post summaries
  2. A comment component for displaying user comments
  3. A pagination component for navigating between pages
  4. A tag-list component for displaying post tags

Use both anonymous and class-based components as appropriate.

Advanced Component Challenge

Create a complex tab-panel component that:

  1. Accepts multiple named slots for tab content
  2. Dynamically generates tab navigation based on the provided tabs
  3. Supports custom styling for active/inactive tabs
  4. Works with JavaScript to show/hide tabs (you can use Alpine.js for simplicity)

Then use this component to create a user profile page with tabs for "Profile", "Orders", and "Settings".

Summary

In the next lecture, we'll explore Eloquent ORM and database interactions in Laravel.