Advanced Blade Components
Introduction to Advanced Blade Components
Blade components provide a powerful way to create reusable UI elements in your Laravel applications. While we've covered basic components earlier, this lesson explores advanced techniques including anonymous components, component classes with methods, dynamic components, slots, and component attributes that will help you build sophisticated, maintainable UIs.
Why Use Advanced Components?
- Create highly reusable and configurable UI elements
- Encapsulate complex logic within component classes
- Pass dynamic content through slots
- Build design systems and component libraries
- Improve code maintainability and readability
- Share components across multiple projects
Component Types Review
Laravel offers two types of Blade components:
- Class-based Components: Components with a PHP class that handles logic and data
- Anonymous Components: Simple components without a class, just a Blade template
Creating Components
# Class-based component
php artisan make:component Alert
# Creates:
# app/View/Components/Alert.php
# resources/views/components/alert.blade.php
# Anonymous component
php artisan make:component forms.input --view
# Creates:
# resources/views/components/forms/input.blade.php
Advanced Component Classes
Component classes can contain complex logic, methods, and computed properties:
<?php
// app/View/Components/UserCard.php
namespace App\View\Components;
use App\Models\User;
use Illuminate\View\Component;
class UserCard extends Component
{
public $user;
public $showEmail;
public $theme;
public function __construct(User $user, $showEmail = false, $theme = 'light')
{
$this->user = $user;
$this->showEmail = $showEmail;
$this->theme = $theme;
}
// Computed property - available in view
public function fullName()
{
return $this->user->first_name . ' ' . $this->user->last_name;
}
// Check if user has avatar
public function hasAvatar()
{
return !empty($this->user->avatar);
}
// Get avatar URL with fallback
public function avatarUrl()
{
return $this->hasAvatar()
? asset('storage/' . $this->user->avatar)
: 'https://ui-avatars.com/api/?name=' . urlencode($this->fullName());
}
// Get theme classes
public function themeClasses()
{
return $this->theme === 'dark'
? 'bg-gray-800 text-white'
: 'bg-white text-gray-900';
}
public function render()
{
return view('components.user-card');
}
}
<!-- resources/views/components/user-card.blade.php -->
<div class="user-card {{ $themeClasses() }}">
<img src="{{ $avatarUrl() }}" alt="{{ $fullName() }}">
<div>
<h3>{{ $fullName() }}</h3>
<p>@{{ $user->username }}</p>
@if($showEmail)
<p>{{ $user->email }}</p>
@endif
</div>
{{ $slot }}
</div>
<!-- Using the component -->
<x-user-card :user="$user" show-email theme="dark">
<button>Follow</button>
</x-user-card>
Method Access: Methods in component classes can be called from the Blade view using parentheses: {{ $fullName() }}. Public properties are accessed without parentheses: {{ $user }}
Anonymous Components
Anonymous components are perfect for simple, presentational components without complex logic:
<!-- resources/views/components/badge.blade.php -->
@props([
'type' => 'default',
'size' => 'md',
])
@php
$classes = match($type) {
'success' => 'bg-green-100 text-green-800',
'danger' => 'bg-red-100 text-red-800',
'warning' => 'bg-yellow-100 text-yellow-800',
'info' => 'bg-blue-100 text-blue-800',
default => 'bg-gray-100 text-gray-800',
};
$sizeClasses = match($size) {
'sm' => 'text-xs px-2 py-1',
'lg' => 'text-base px-4 py-2',
default => 'text-sm px-3 py-1',
};
@endphp
<span {{ $attributes->merge(['class' => "badge $classes $sizeClasses rounded-full"]) }}>
{{ $slot }}
</span>
<!-- Usage -->
<x-badge type="success">Active</x-badge>
<x-badge type="danger" size="lg">Critical</x-badge>
<x-badge type="warning" class="ml-2">Pending</x-badge>
Component Attributes
The $attributes variable gives you fine-grained control over HTML attributes:
<!-- resources/views/components/button.blade.php -->
@props([
'type' => 'button',
'variant' => 'primary',
'size' => 'md',
'disabled' => false,
])
@php
$variantClasses = match($variant) {
'primary' => 'bg-blue-600 hover:bg-blue-700 text-white',
'secondary' => 'bg-gray-600 hover:bg-gray-700 text-white',
'danger' => 'bg-red-600 hover:bg-red-700 text-white',
'outline' => 'border border-gray-300 hover:bg-gray-50 text-gray-700',
};
$sizeClasses = match($size) {
'sm' => 'px-3 py-1 text-sm',
'lg' => 'px-6 py-3 text-lg',
default => 'px-4 py-2 text-base',
};
$baseClasses = 'rounded font-medium focus:outline-none focus:ring-2 transition';
@endphp
<button
type="{{ $type }}"
{{ $attributes->class([$baseClasses, $variantClasses, $sizeClasses])
->merge(['disabled' => $disabled]) }}>
{{ $slot }}
</button>
<!-- Usage examples -->
<x-button>Default Button</x-button>
<x-button variant="danger" size="lg">Delete</x-button>
<x-button variant="outline" class="mt-4" id="submit-btn">Submit</x-button>
<x-button type="submit" :disabled="$processing">Save</x-button>
Attribute Methods:
$attributes->merge()- Merges default attributes with passed attributes$attributes->class()- Conditionally merges CSS classes$attributes->filter()- Filters specific attributes$attributes->only()- Gets only specified attributes$attributes->except()- Gets all except specified attributes
Named Slots
Named slots allow you to pass multiple content sections to a component:
<!-- resources/views/components/card.blade.php -->
<div {{ $attributes->merge(['class' => 'card border rounded-lg shadow-sm']) }}>
@if(isset($header))
<div class="card-header border-b px-6 py-4 bg-gray-50">
{{ $header }}
</div>
@endif
<div class="card-body p-6">
{{ $slot }}
</div>
@if(isset($footer))
<div class="card-footer border-t px-6 py-4 bg-gray-50">
{{ $footer }}
</div>
@endif
</div>
<!-- Using named slots -->
<x-card>
<x-slot:header>
<h2 class="text-xl font-bold">User Profile</h2>
</x-slot:header>
<!-- Default slot content -->
<p>This is the main content of the card.</p>
<p>User information goes here.</p>
<x-slot:footer>
<button>Edit Profile</button>
<button>Delete Account</button>
</x-slot:footer>
</x-card>
Scoped Slots
Scoped slots allow parent components to pass data to slot content:
<!-- resources/views/components/dropdown.blade.php -->
@props(['items'])
<div class="dropdown" x-data="{ open: false }">
<button @click="open = !open">
{{ $trigger }}
</button>
<div x-show="open" @click.away="open = false" class="dropdown-menu">
@foreach($items as $item)
{{ $slot(['item' => $item]) }}
@endforeach
</div>
</div>
<!-- Using scoped slots -->
<x-dropdown :items="$users">
<x-slot:trigger>
Select User
</x-slot:trigger>
<!-- Scoped slot with access to $item -->
@scope($item)
<a href="/users/{{ $item->id }}">
{{ $item->name }}
</a>
@endscope
</x-dropdown>
Note: Scoped slots are an advanced feature. For most use cases, regular slots or passing data as props is simpler and more maintainable.
Dynamic Components
Render components dynamically based on runtime values:
<!-- Dynamic component based on variable -->
@php
$componentType = $user->is_premium ? 'premium-badge' : 'regular-badge';
@endphp
<x-dynamic-component :component="$componentType" :user="$user" />
<!-- Dynamic component in loop -->
@foreach($notifications as $notification)
<x-dynamic-component
:component="'notifications.' . $notification->type"
:notification="$notification"
/>
@endforeach
<?php
// Controller or service
class NotificationRenderer
{
public function render($notification)
{
$componentMap = [
'message' => 'notifications.message',
'friend_request' => 'notifications.friend-request',
'order_update' => 'notifications.order-update',
];
$component = $componentMap[$notification->type] ?? 'notifications.default';
return view()->make('components.' . $component, [
'notification' => $notification
]);
}
}
Component Stacks
Stacks allow you to push content from child components to parent layouts:
<!-- resources/views/layouts/app.blade.php -->
<html>
<head>
<!-- Styles stack -->
@stack('styles')
</head>
<body>
{{ $slot }}
<!-- Scripts stack -->
@stack('scripts')
</body>
</html>
<!-- resources/views/components/chart.blade.php -->
<div class="chart" id="chart-{{ $id }}"></div>
@once
@push('styles')
<link rel="stylesheet" href="/css/charts.css">
@endpush
@push('scripts')
<script src="/js/chart.js"></script>
@endpush
@endonce
@push('scripts')
<script>
initChart('{{ $id }}', @json($data));
</script>
@endpush
@once Directive: Use @once to ensure code is only pushed to the stack once, even if the component is rendered multiple times on the page.
Building a Form Component Library
Create a complete form component system:
<!-- resources/views/components/forms/input.blade.php -->
@props([
'name',
'label' => null,
'type' => 'text',
'value' => '',
'placeholder' => '',
'required' => false,
'disabled' => false,
])
<div class="form-group mb-4">
@if($label)
<label for="{{ $name }}" class="block text-sm font-medium text-gray-700 mb-2">
{{ $label }}
@if($required)
<span class="text-red-500">*</span>
@endif
</label>
@endif
<input
type="{{ $type }}"
name="{{ $name }}"
id="{{ $name }}"
value="{{ old($name, $value) }}"
placeholder="{{ $placeholder }}"
{{ $required ? 'required' : '' }}
{{ $disabled ? 'disabled' : '' }}
{{ $attributes->class([
'w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500',
'border-red-500' => $errors->has($name),
'border-gray-300' => !$errors->has($name),
]) }}
>
@error($name)
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- resources/views/components/forms/select.blade.php -->
@props([
'name',
'label' => null,
'options' => [],
'selected' => null,
'placeholder' => 'Select an option',
'required' => false,
])
<div class="form-group mb-4">
@if($label)
<label for="{{ $name }}" class="block text-sm font-medium text-gray-700 mb-2">
{{ $label }}
@if($required) <span class="text-red-500">*</span> @endif
</label>
@endif
<select
name="{{ $name }}"
id="{{ $name }}"
{{ $required ? 'required' : '' }}
{{ $attributes->class([
'w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500',
'border-red-500' => $errors->has($name),
'border-gray-300' => !$errors->has($name),
]) }}
>
<option value="">{{ $placeholder }}</option>
@foreach($options as $value => $text)
<option value="{{ $value }}" {{ old($name, $selected) == $value ? 'selected' : '' }}>
{{ $text }}
</option>
@endforeach
</select>
@error($name)
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<!-- Using form components -->
<form method="POST" action="/users">
@csrf
<x-forms.input
name="name"
label="Full Name"
placeholder="Enter your full name"
required
/>
<x-forms.input
name="email"
type="email"
label="Email Address"
placeholder="your@email.com"
required
/>
<x-forms.select
name="country"
label="Country"
:options="$countries"
:selected="$user->country ?? null"
required
/>
<x-button type="submit" variant="primary">Create User</x-button>
</form>
Practice Exercise 1: Alert Component System
Create a comprehensive alert component with the following features:
- Support types: success, error, warning, info
- Optional icon based on type
- Dismissible option with Alpine.js
- Optional title and description slots
- Action buttons slot
- Automatic timeout for dismissal (optional prop)
Example usage:
<x-alert type="success" dismissible :timeout="5000">
<x-slot:title>Success!</x-slot:title>
Your changes have been saved.
</x-alert>
Practice Exercise 2: Data Table Component
Build a reusable data table component:
- Accept columns configuration (name, label, sortable)
- Accept rows data
- Support sortable columns with icons
- Pagination controls
- Empty state message
- Action column slot for custom buttons
- Optional search filter
Bonus: Make it work with Livewire for dynamic sorting and filtering.
Practice Exercise 3: Modal Component
Create a flexible modal component:
- Use Alpine.js for open/close functionality
- Support sizes: sm, md, lg, xl, full
- Named slots: trigger, title, body, footer
- Close button in header
- Backdrop click to close (optional)
- ESC key to close
- Focus trap when open
- Body scroll lock when modal is open
Challenge: Make it work with both Alpine.js and Livewire wire:click triggers.
Summary
In this lesson, you mastered advanced Blade component techniques. Key concepts covered:
- Building class-based components with complex logic and methods
- Creating lightweight anonymous components for simple use cases
- Managing component attributes with merge, class, and filter methods
- Using named slots to pass multiple content sections
- Implementing scoped slots for data sharing
- Rendering dynamic components based on runtime conditions
- Leveraging component stacks for assets management
- Building comprehensive component libraries (forms, alerts, cards)
- Best practices for reusable, maintainable components
These advanced component techniques enable you to build sophisticated, design-system-level UI components that are reusable across your entire application. In the next lesson, we'll explore database optimization and performance techniques.