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.
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>© {{ 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:
- @yield - Creates placeholders that child views can fill
- @section/@show - Defines default content that child views can override or extend
- @include - Embeds partial views
- @stack - Creates a place for child views to push content like scripts
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:
- @extends - Specifies which layout to use as the parent
- @section('title', 'Home Page') - Short syntax for simple content
- @section('content')...@endsection - Fills the content placeholder
- @section('sidebar')...@parent...@endsection - Appends content to the parent's sidebar section
- @push('scripts') - Adds scripts to the stack defined in the parent
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.
Laravel offers two types of components:
- Class-based components - PHP classes paired with a view, offering more control and logic
- 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:
$type- An attribute with a default value of 'info'$title- A specific attribute for the alert title$slot- The main content passed between the component tags
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:
- Logic Encapsulation - Keep component-specific logic in the class
- Input Processing - Transform or validate input attributes
- Helper Methods - Provide view-specific methods like
isImportant() - Dependency Injection - Inject services via the constructor
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:
- Label generation
- Old input persistence
- Error state and messaging
- Attribute merging for additional HTML attributes
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:
- Template inheritance with
@extendsand@section - Pushing styles and scripts to stacks
- Using both simple and complex components
- Component attributes and slots
- Including partial views for reusable content
- Conditional rendering based on product properties
- Authentication-based display with
@auth
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
- Keep Layouts Simple - Focus on structure, not specific content
- Use Multiple Layouts - Create different layouts for different sections (admin, auth, etc.)
- Use Stacks Strategically - Place stacks where child views are likely to need them
- Leverage Includes - Break complex layouts into manageable partial views
Component Best Practices
- Single Responsibility - Each component should do one thing well
- Self-Contained - Components should work independently without external dependencies
- Use Props with Defaults - Make components flexible but provide sensible defaults
- Consistent Naming - Establish a naming convention for your components
- Document Usage - Add comments to complex components explaining their usage
- Organize by Function - Group components by their purpose (ui, form, layout, etc.)
Practice Activity
Master Layout Creation
Create a master layout for a blog application that includes:
- A header with navigation
- A main content area
- A sidebar with configurable content
- A footer with copyright information
- 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:
- A
post-cardcomponent for displaying post summaries - A
commentcomponent for displaying user comments - A
paginationcomponent for navigating between pages - A
tag-listcomponent for displaying post tags
Use both anonymous and class-based components as appropriate.
Advanced Component Challenge
Create a complex tab-panel component that:
- Accepts multiple named slots for tab content
- Dynamically generates tab navigation based on the provided tabs
- Supports custom styling for active/inactive tabs
- 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
- Template inheritance lets you define a master layout and extend it in child views
- The
@yielddirective creates content placeholders that child views can fill - The
@section/@showpattern provides default content that child views can modify - Blade components offer a more modular, reusable approach to view composition
- Anonymous components are simple Blade templates without associated classes
- Class-based components pair a Blade template with a PHP class for more logic
- Components can accept attributes, have slots, and include helper methods
- Organizing components into namespaces helps manage larger applications
- Dynamic components allow runtime selection of which component to render
In the next lecture, we'll explore Eloquent ORM and database interactions in Laravel.