Laravel Framework

Advanced Blade Components

15 min Lesson 33 of 45

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.