
Laravel Blade
- 160 installs
- 22 repo stars
- Updated August 3, 2026
- fusengine/agents
For development and infrastructure management.
About
laravel-blade is an AI coding tool that enhances development workflows. Builders use it for infrastructure, integration, and platform development within the catalog ecosystem.
- laravel-blade
- Development
Laravel Blade by the numbers
- 160 all-time installs (skills.sh)
- Ranked #2,343 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/fusengine/agents --skill laravel-bladeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 160 |
|---|---|
| repo stars | ★ 22 |
| Last updated | August 3, 2026 |
| Repository | fusengine/agents ↗ |
What it does
For development and infrastructure management.
Files
Laravel Blade
Agent Workflow (MANDATORY)
Before ANY implementation, use TeamCreate to spawn 3 agents:
1. fuse-ai-pilot:explore-codebase - Check existing views, components structure 2. fuse-ai-pilot:research-expert - Verify latest Blade docs via Context7 3. mcp__context7__query-docs - Query specific patterns (components, slots)
After implementation, run fuse-ai-pilot:sniper for validation.
---
Overview
Blade is Laravel's templating engine. It provides a clean syntax for PHP in views while compiling to pure PHP for performance.
| Component Type | When to Use |
|---|---|
| Anonymous | Simple UI, no logic needed |
| Class-based | Dependency injection, complex logic |
| Layout | Page structure, reusable shells |
| Dynamic | Runtime component selection |
---
Critical Rules
1. Always escape output - Use {{ }} not {!! !!} unless absolutely necessary 2. Use @props - Declare expected props explicitly 3. Merge attributes - Allow class/attribute overrides with $attributes->merge() 4. Prefer anonymous - Use class components only when logic is needed 5. Use named slots - For complex layouts with multiple content areas 6. CSRF in forms - Always include @csrf in forms
---
Decision Guide
Component Type Selection
Need dependency injection?
├── YES → Class-based component
└── NO → Anonymous component
│
Need complex props logic?
├── YES → Class-based component
└── NO → Anonymous componentLayout Strategy
Simple page structure?
├── YES → Component layout (<x-layout>)
└── NO → Need fine-grained sections?
├── YES → @extends/@section
└── NO → Component layout---
Key Concepts
| Concept | Description | Reference |
|---|---|---|
| @props | Declare component properties | components.md |
| $attributes | Pass-through HTML attributes | slots-attributes.md |
| x-slot | Named content areas | slots-attributes.md |
| @yield/@section | Traditional layout inheritance | layouts.md |
| $loop | Loop iteration info | directives.md |
---
Reference Guide
Concepts (WHY & Architecture)
| Topic | Reference | When to Consult |
|---|---|---|
| Components | components.md | Class vs anonymous, namespacing |
| Slots & Attributes | slots-attributes.md | Data flow, $attributes bag |
| Layouts | layouts.md | Page structure, inheritance |
| Directives | directives.md | @if, @foreach, @auth, @can |
| Security | security.md | XSS, CSRF, escaping |
| Vite | vite.md | Asset bundling |
| Advanced Directives | advanced-directives.md | @once, @use, @inject, @switch, stacks |
| Custom Directives | custom-directives.md | Blade::if, Blade::directive |
| Advanced Components | advanced-components.md | @aware, shouldRender, index |
| Forms & Validation | forms-validation.md | @error, form helpers |
| Fragments | fragments.md | @fragment, HTMX integration |
Templates (Complete Code)
| Template | When to Use |
|---|---|
| ClassComponent.php.md | Component with logic/DI |
| AnonymousComponent.blade.md | Simple reusable UI |
| LayoutComponent.blade.md | Page layout structure |
| FormComponent.blade.md | Form with validation |
| CardWithSlots.blade.md | Named slots pattern |
| DynamicComponent.blade.md | Runtime component |
| AdvancedDirectives.blade.md | @once, @use, @inject, @switch |
| CustomDirectives.php.md | Create custom directives |
| AdvancedComponents.blade.md | @aware, shouldRender, index |
| Fragments.blade.md | HTMX partial updates |
---
Quick Reference
Anonymous Component
{{-- resources/views/components/alert.blade.php --}}
@props(['type' => 'info', 'message'])
<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
{{ $message }}
</div>Class Component
// app/View/Components/Alert.php
class Alert extends Component
{
public function __construct(
public string $type = 'info',
public string $message = ''
) {}
public function render(): View
{
return view('components.alert');
}
}Named Slots
<x-card>
<x-slot:header class="font-bold">
Title
</x-slot>
Content goes here
<x-slot:footer>
Footer content
</x-slot>
</x-card>Attribute Merging
@props(['disabled' => false])
<button {{ $attributes->merge([
'type' => 'submit',
'class' => 'btn btn-primary'
])->class(['opacity-50' => $disabled]) }}
@disabled($disabled)
>
{{ $slot }}
</button>---
Best Practices
DO
- Use
@propsto document expected props - Use
$attributes->merge()for flexibility - Prefer anonymous components for simple UI
- Use named slots for complex layouts
- Keep components focused and reusable
DON'T
- Use
{!! !!}without sanitization - Forget
@csrfin forms - Put business logic in Blade templates
- Create deeply nested component hierarchies
- Hardcode classes (allow overrides)
---
Laravel 13 Notes
Attributes Controllers #[Middleware] et #[Authorize]
Laravel 13 supporte les attributs PHP sur les classes ET méthodes de controllers pour déclarer middleware et autorisations (remplace __construct middleware boilerplate).
use Illuminate\Routing\Attributes\Middleware;
use Illuminate\Routing\Attributes\Authorize;
#[Middleware(['auth', 'verified'])]
final class PostController extends Controller
{
#[Authorize('viewAny', Post::class)]
public function index() { return view('posts.index'); }
#[Middleware('throttle:60,1')]
#[Authorize('create', Post::class)]
public function store(StorePostRequest $request) { /* ... */ }
}Le middleware passé via attribut s'applique avant les middlewares groupes/routes. #[Authorize] jette AuthorizationException (403) si la policy refuse.
Advanced Components
Decision Tree - Data Flow
Need parent component data in child?
├── YES → @aware(['color'])
└── NO → Use @props normallyDecision Tree - Conditional Render
Component should hide based on logic?
├── YES → shouldRender() in class
└── NO → Always render@aware - Parent Data Access
| Use When | Purpose |
|---|---|
| Menu → MenuItem | Child inherits menu color |
| Accordion → AccordionItem | Child knows parent state |
| Tabs → TabPanel | Child knows active tab |
| Card → CardHeader | Child inherits card style |
| vs @props | @aware |
|---|---|
| Declared props | Inherited from parent |
| Passed explicitly | Passed implicitly |
| Required in usage | Automatic |
shouldRender() Method
| Use When | Purpose |
|---|---|
| Alert with no message | Hide completely |
| Feature flag component | Show based on flag |
| Permission-based UI | Hide unauthorized |
| Empty state | Hide wrapper |
| Returns | Result |
|---|---|
true | Component renders |
false | Component hidden (no HTML) |
Index Components
| Structure | Usage |
|---|---|
components/card/index.blade.php | <x-card> |
components/form/index.blade.php | <x-form> |
components/modal/index.blade.php | <x-modal> |
| Benefit | Description |
|---|---|
| Cleaner structure | Related files together |
| Same usage | No change in templates |
| Sub-components | card/header.blade.php → <x-card.header> |
Component Namespacing
| Scope | Registration | Usage |
|---|---|---|
| Package | Blade::anonymousComponentPath(path, 'pkg') | <x-pkg::button> |
| Module | Blade::componentNamespace('Namespace', 'module') | <x-module::alert> |
Render Conditions
| Method | Use When |
|---|---|
shouldRender() | Class component |
@if in template | Anonymous component |
@props + check | Props-based condition |
Component Accessors
| Property | Purpose |
|---|---|
$component->data | All component data |
$component->name | Component name |
$component->attributes | Attribute bag |
$component->slot | Default slot |
Nested Component Patterns
| Pattern | Use When |
|---|---|
Parent passes $color | All children need same style |
Child uses @aware | Inherit without explicit pass |
| Scoped slots | Child passes data to parent |
→ Code examples: See templates/AdvancedComponents.blade.md
Advanced Directives
Decision Tree - Imports
Need PHP class in template?
├── YES → @use('App\Models\User')
└── NO → Need service/container?
├── YES → @inject('service', 'App\Services\...')
└── NO → Standard variablesDecision Tree - Control Flow
Multiple conditions on same variable?
├── YES → @switch/@case
└── NO → @if/@elseif@use - Class Imports
| Syntax | Use When |
|---|---|
@use('App\Models\User') | Import single class |
@use('App\Models\User', 'UserModel') | With alias |
@use('App\Enums\Status') | Import enums |
@inject - Service Injection
| Syntax | Use When |
|---|---|
@inject('service', 'App\Services\MetricsService') | Need service methods |
@inject('carbon', 'Carbon\Carbon') | Need utility class |
@once - Prevent Duplication
| Use When | Purpose |
|---|---|
| Scripts in loops | Load JS once |
| Styles in components | Load CSS once |
| Init code in partials | Run setup once |
@switch - Multiple Conditions
| Directive | Purpose |
|---|---|
@switch($var) | Start switch |
@case('value') | Match case |
@break | Exit case |
@default | Fallback |
@endswitch | End switch |
@verbatim - Escape Blade
| Use When | Framework |
|---|---|
| Vue.js templates | {{ }} conflicts |
| Alpine.js | x-text with {{ }} |
| Any JS framework | Double braces |
Advanced Stacks
| Directive | Use When |
|---|---|
@push('name') | Add to end of stack |
@prepend('name') | Add to start of stack |
@pushIf($cond, 'name') | Conditional push |
@hasStack('name') | Check stack not empty |
Environment Directives
| Directive | Use When |
|---|---|
@env('local') | Single environment |
@env(['local', 'staging']) | Multiple environments |
@production | Production only |
Session Directives
| Directive | Use When |
|---|---|
@session('key') | Display session flash |
Common Patterns
| Pattern | Directives |
|---|---|
| Load chart.js once in loop | @once + @push |
| Admin-only analytics | @env('production') + @can |
| Vue app in Blade | @verbatim |
| Role-based panels | @switch on role |
| Service data display | @inject |
→ Code examples: See templates/AdvancedDirectives.blade.md
Blade Components
Decision Tree
Need dependency injection?
├── YES → Class-based component
└── NO → Need computed properties?
├── YES → Class-based component
└── NO → Anonymous component (default)Component Types
| Type | Use When | Template |
|---|---|---|
| Anonymous | Simple UI, no logic | AnonymousComponent.blade.md |
| Class-based | DI, services, computed props | ClassComponent.php.md |
| Dynamic | Runtime component selection | DynamicComponent.blade.md |
When to Use What
| Scenario | Type | Why |
|---|---|---|
| Button, card, alert | Anonymous | No logic needed |
| User avatar with API call | Class-based | Needs service injection |
| Form field based on type | Dynamic | Runtime selection |
| Layout wrapper | Anonymous | Just slots |
| Stats with calculations | Class-based | Computed properties |
Artisan Commands
| Action | Command |
|---|---|
| Create anonymous | php artisan make:component alert --view |
| Create class-based | php artisan make:component Alert |
| Create nested | php artisan make:component Form/Input --view |
File Locations
| Type | PHP Class | Blade View |
|---|---|---|
| Anonymous | None | resources/views/components/ |
| Class-based | app/View/Components/ | resources/views/components/ |
| Nested | app/View/Components/Form/ | resources/views/components/form/ |
Usage Syntax
| Component Location | Usage |
|---|---|
components/alert.blade.php | <x-alert /> |
components/form/input.blade.php | <x-form.input /> |
components/card/index.blade.php | <x-card /> |
| Package component | <x-package::button /> |
Key Concepts
| Concept | Purpose | See |
|---|---|---|
@props | Declare expected props | slots-attributes.md |
$attributes | Pass-through HTML attrs | slots-attributes.md |
$slot | Default content area | slots-attributes.md |
shouldRender() | Conditional rendering | ClassComponent.php.md |
→ Code examples: See templates/
Custom Directives
Decision Tree
Need custom conditional (@something/@endsomething)?
├── YES → Blade::if('name', callback)
└── NO → Need custom output (@directive)?
├── YES → Blade::directive('name', callback)
└── NO → Use existing directivesBlade::if - Custom Conditionals
| Use When | Example |
|---|---|
| Check app config | @disk('s3') |
| Check feature flags | @feature('dark-mode') |
| Check user roles | @role('admin') |
| Check subscriptions | @subscribed('pro') |
| Generated Directives | Purpose |
|---|---|
@name | If true |
@elsename | Else if |
@endname | End block |
Blade::directive - Custom Output
| Use When | Example |
|---|---|
| Format dates | @datetime($date) |
| Format money | @money($amount) |
| Render markdown | @markdown($content) |
| Generate URLs | @route('name') |
Registration Location
| Location | When |
|---|---|
AppServiceProvider::boot() | Simple directives |
Dedicated BladeServiceProvider | Many directives |
Return Value Format
| Type | Must Return |
|---|---|
Blade::if() | bool |
Blade::directive() | string (PHP code) |
Common Custom Directives
| Directive | Purpose |
|---|---|
@datetime($date) | Format Carbon dates |
@money($amount, $currency) | Format currency |
@markdown($text) | Render markdown |
@nl2br($text) | Newlines to <br> |
@truncate($text, $length) | Truncate with ellipsis |
Common Custom Conditionals
| Conditional | Purpose |
|---|---|
@admin | Current user is admin |
@subscribed('plan') | User has subscription |
@feature('flag') | Feature flag enabled |
@disk('driver') | Check storage driver |
@locale('en') | Check app locale |
Best Practices
| DO | DON'T |
|---|---|
| Keep directives simple | Complex logic in directives |
| Use for repeated patterns | One-off formatting |
| Document in SKILL.md | Undocumented directives |
| Test directive output | Assume it works |
→ Code examples: See templates/CustomDirectives.php.md
Blade Directives
Decision Tree - Conditionals
Check authentication?
├── YES → @auth / @guest
└── NO → Check authorization?
├── YES → @can / @cannot / @canany
└── NO → @if / @unless / @isset / @emptyDecision Tree - Loops
Need empty state?
├── YES → @forelse
└── NO → Need index?
├── YES → @for or @foreach with $loop
└── NO → @foreachConditional Directives
| Directive | Use When |
|---|---|
@if / @elseif / @else | Standard conditions |
@unless | Negated condition |
@isset($var) | Check defined and not null |
@empty($var) | Check "empty" |
Authentication Directives
| Directive | Use When |
|---|---|
@auth | User is logged in |
@guest | User is not logged in |
@auth('admin') | Specific guard |
Authorization Directives
| Directive | Use When |
|---|---|
@can('edit', $post) | Check single ability |
@elsecan('create', Model::class) | Chain abilities |
@cannot('delete', $post) | Check cannot |
@elsecannot('view', $post) | Chain cannot |
@canany(['edit', 'delete'], $post) | Any ability matches |
@elsecanany(['create'], Model::class) | Chain any |
Loop Directives
| Directive | Use When |
|---|---|
@foreach | Iterate collection |
@forelse | With empty state |
@for | Index-based |
@while | Condition-based |
@continue | Skip iteration |
@continue($cond) | Conditional skip |
@break | Exit loop |
@break($cond) | Conditional exit |
$loop Variable
| Property | Returns |
|---|---|
$loop->index | 0-based index |
$loop->iteration | 1-based index |
$loop->first | Is first? |
$loop->last | Is last? |
$loop->even / $loop->odd | Parity |
$loop->count | Total items |
$loop->remaining | Items left |
$loop->depth | Nesting level |
$loop->parent | Parent's $loop |
Attribute Directives
| Directive | Use When |
|---|---|
@class([...]) | Conditional classes |
@style([...]) | Conditional styles |
@checked($bool) | Checkbox/radio |
@selected($bool) | Select option |
@disabled($bool) | Disable element |
@readonly($bool) | Read-only input |
@required($bool) | Required field |
Include Directives
| Directive | Use When |
|---|---|
@include('view') | Always include |
@includeIf('view') | If exists |
@includeWhen($cond, 'view') | Conditional |
@includeUnless($cond, 'view') | Negated |
@includeFirst(['a', 'b']) | First that exists |
@each('view', $items, 'item') | Loop include |
@each('view', $items, 'item', 'empty') | With empty view |
Other Directives
| Directive | Use When |
|---|---|
@php ... @endphp | Raw PHP |
@{{ var }} | Escape for Vue/Alpine |
@verbatim | Escape block |
@env('production') | Check environment |
@production / @env('local') | Shortcuts |
→ Code examples: See templates/
Forms & Validation
Decision Tree - Error Display
Need to show validation error?
├── YES → @error('field')
│ │
│ Need custom error bag?
│ ├── YES → @error('field', 'bagName')
│ └── NO → @error('field')
└── NO → Direct output@error Directive
| Syntax | Use When |
|---|---|
@error('field') | Default error bag |
@error('field', 'login') | Named error bag |
$message | Error message variable |
| Pattern | Purpose |
|---|---|
| Add class on error | class="@error('email') is-invalid @enderror" |
| Show error message | @error('email') {{ $message }} @enderror |
| With else | @error('email') invalid @else valid @enderror |
Form Attribute Directives
| Directive | HTML Output | Use When |
|---|---|---|
@checked($bool) | checked | Checkbox/radio state |
@selected($bool) | selected | Select option state |
@disabled($bool) | disabled | Disable input |
@readonly($bool) | readonly | Read-only input |
@required($bool) | required | Required field |
Common Patterns
| Pattern | Directive Combo |
|---|---|
| Preserve old input | @checked(old('remember')) |
| Selected option | @selected(old('role') === $value) |
| Admin-only edit | @readonly(!$user->isAdmin()) |
| Conditional required | @required($field->isRequired) |
| Disable on processing | @disabled($processing) |
Error Display Patterns
| Pattern | Use When |
|---|---|
| Inline error | Below input field |
| Error summary | Top of form |
| Field highlighting | Border/background color |
| Icon indicator | Visual error marker |
old() Helper
| Syntax | Purpose |
|---|---|
old('name') | Previous input value |
old('name', $default) | With fallback |
old('items.0.name') | Array input |
Best Practices
| DO | DON'T |
|---|---|
| Use @error for each field | Forget error feedback |
| Combine with old() | Lose user input on error |
| Style invalid fields | Only show text errors |
| Use named bags for forms | Mix error bags |
→ Code examples: See FormComponent.blade.md
Fragments (Laravel 13+)
Decision Tree
Need partial page update?
├── YES → Using HTMX/Turbo?
│ ├── YES → @fragment + ->fragment()
│ └── NO → Using Livewire?
│ ├── YES → Livewire components
│ └── NO → Consider @fragment for AJAX
└── NO → Standard full-page renderOverview
| Feature | Purpose |
|---|---|
@fragment('name') | Define reusable section |
->fragment('name') | Return only that section |
->fragmentIf($cond, 'name') | Conditional fragment |
->fragments(['a', 'b']) | Return multiple fragments |
@fragment Directive
| Syntax | Purpose |
|---|---|
@fragment('name') | Start fragment |
@endfragment | End fragment |
Controller Methods
| Method | Use When |
|---|---|
->fragment('name') | Always return fragment |
->fragmentIf($bool, 'name') | Conditional (HTMX request) |
->fragments(['a', 'b']) | Multiple fragments |
HTMX Integration
| Header | Purpose |
|---|---|
HX-Request | Detect HTMX request |
HX-Target | Target element ID |
HX-Trigger | Trigger element |
Common Patterns
| Pattern | Use Case |
|---|---|
| List refresh | Update table without reload |
| Form feedback | Show success/error message |
| Infinite scroll | Load more items |
| Live search | Update results on type |
| Tab content | Load tab on click |
Best Practices
| DO | DON'T |
|---|---|
| Name fragments descriptively | Use generic names |
| Check for HTMX header | Always return fragment |
| Keep fragments focused | Make fragments too large |
| Test full-page fallback | Break non-JS users |
vs Livewire
| Feature | Fragments | Livewire |
|---|---|---|
| Complexity | Low | Medium |
| Real-time | HTMX polling | WebSockets |
| State | Stateless | Stateful |
| Dependencies | None | Livewire package |
→ Code examples: See Fragments.blade.md
Layouts
Decision Tree
Need fine-grained sections (@yield)?
├── YES → Template inheritance (@extends)
└── NO → Component layout (<x-layout>) ← RECOMMENDED
│
Need multiple stacks (scripts, styles)?
├── YES → @stack/@push in either approach
└── NO → Simple component layoutLayout Approaches
| Approach | Use When | Template |
|---|---|---|
Component <x-layout> | Modern apps, simple structure | LayoutComponent.blade.md |
Inheritance @extends | Complex sections, legacy apps | LayoutComponent.blade.md |
Comparison
| Feature | Component | Inheritance |
|---|---|---|
| Syntax | <x-layout> | @extends('layout') |
| Content areas | Named slots | @section/@yield |
| Props | <x-layout title="..."> | @section('title', '...') |
| Default content | {{ $header ?? '' }} | @yield('header') |
| Extensibility | Composition | @parent directive |
Key Directives
| Directive | Purpose | Used In |
|---|---|---|
@yield('name') | Placeholder for section | Layout file |
@yield('name', 'default') | With default value | Layout file |
@section('name')...@endsection | Define content | Page file |
@section('name', 'value') | Short syntax | Page file |
@parent | Include parent's content | Page file |
@stack('name') | Stack placeholder | Layout file |
@push('name') | Add to stack | Page file |
@prepend('name') | Prepend to stack | Page file |
File Organization
| File | Location | Purpose |
|---|---|---|
| App layout | components/layouts/app.blade.php | Main shell |
| Guest layout | components/layouts/guest.blade.php | Auth pages |
| Admin layout | components/layouts/admin.blade.php | Dashboard |
| Page | pages/dashboard.blade.php | Uses layout |
Common Patterns
| Pattern | Solution |
|---|---|
| Page title | Prop: <x-layout title="Dashboard"> |
| Meta tags | Named slot: <x-slot:meta> |
| Scripts at end | Stack: @push('scripts') |
| Breadcrumbs | Named slot: <x-slot:breadcrumb> |
| Sidebar | Named slot or prop |
→ Code examples: See LayoutComponent.blade.md
Blade Security
Decision Tree - Output
Is it user content?
├── YES → Use {{ }} (escaped)
└── NO → Is it trusted HTML?
├── YES → Sanitize first, then {!! !!}
└── NO → Use {{ }} anyway (safe default)Output Escaping
| Syntax | Behavior | Use When |
|---|---|---|
{{ $var }} | HTML escaped | User content (DEFAULT) |
{!! $var !!} | Raw output | Sanitized HTML only |
@json($data) | JSON encoded | JavaScript data |
{{ e($var) }} | Explicit escape | When needed |
Security Rules
| Content Type | Required Action |
|---|---|
| User input | ALWAYS {{ }} |
| Database text | ALWAYS {{ }} |
| Request data | ALWAYS {{ }} |
| Admin HTML | Sanitize + {!! !!} |
| Markdown output | Sanitize + {!! !!} |
| Static strings | {{ }} or raw |
CSRF Protection
| Form Type | Required |
|---|---|
| POST | @csrf |
| PUT | @csrf + @method('PUT') |
| PATCH | @csrf + @method('PATCH') |
| DELETE | @csrf + @method('DELETE') |
| GET | None |
JavaScript Data
| Method | Use When |
|---|---|
@json($data) | Pass data to JS |
@json($data, JSON_PRETTY_PRINT) | Debug output |
data-config='@json($config)' | In attributes |
Vulnerability Prevention
| Vulnerability | Prevention |
|---|---|
| XSS | Always {{ }} for user content |
| CSRF | Always @csrf in forms |
| Open redirect | Validate URL domains |
| HTML injection | HTMLPurifier before {!! !!} |
| JS injection | Use @json() for data |
| Path traversal | Never use user input in paths |
Sanitization
| Need | Solution |
|---|---|
| Rich text (WYSIWYG) | HTMLPurifier |
| Markdown | League\CommonMark with safe mode |
| Plain text with links | Linkify after escaping |
Checklist
| Check | Requirement |
|---|---|
| User content | {{ }} |
| Forms | @csrf |
| URLs in href | Validate protocol |
| Raw HTML | Sanitize first |
| JS data | @json() |
| File paths | Never from user input |
→ Code examples: See FormComponent.blade.md
Slots & Attributes
Decision Tree - Slots
Component needs multiple content areas?
├── YES → Named slots (<x-slot:name>)
└── NO → Default slot ($slot)
│
Slot needs attributes?
├── YES → <x-slot:name class="...">
└── NO → Simple contentDecision Tree - Attributes
Need to accept HTML attributes?
├── YES → Use $attributes
│ │
│ Need default classes?
│ ├── YES → $attributes->merge()
│ └── NO → {{ $attributes }}
└── NO → Just use @propsSlot Types
| Type | Use When | Template |
|---|---|---|
| Default | Single content area | AnonymousComponent.blade.md |
| Named | Header/body/footer pattern | CardWithSlots.blade.md |
| Scoped | Pass data back to parent | CardWithSlots.blade.md |
Attribute Methods
| Method | Use When |
|---|---|
{{ $attributes }} | Render all attributes |
$attributes->merge([...]) | Add defaults, allow overrides |
$attributes->class([...]) | Conditional CSS classes |
$attributes->only(['id']) | Filter specific attributes |
$attributes->except(['class']) | Exclude attributes |
$attributes->has('wire:model') | Check attribute exists |
$attributes->get('id', 'default') | Get with fallback |
Slot Methods
| Method | Use When |
|---|---|
$slot->isEmpty() | Hide wrapper if empty |
$slot->isNotEmpty() | Show only with content |
$header->attributes | Access slot's attributes |
@props vs $attributes
| Declared in @props | Behavior |
|---|---|
| YES | Becomes variable, removed from $attributes |
| NO | Stays in $attributes for pass-through |
Common Patterns
| Pattern | Solution | Template |
|---|---|---|
| Card with header/footer | Named slots | CardWithSlots.blade.md |
| Button with merged classes | $attributes->merge() | FormComponent.blade.md |
| Conditional wrapper | $slot->isNotEmpty() | LayoutComponent.blade.md |
| Form input pass-through | {{ $attributes }} | FormComponent.blade.md |
→ Code examples: See templates/
Advanced Components - Complete Examples
@aware - Parent Data Inheritance
Parent Component - Menu
{{-- resources/views/components/menu.blade.php --}}
@props([
'color' => 'gray',
'size' => 'md'
])
<nav {{ $attributes->merge(['class' => "menu menu-{$size}"]) }}>
<ul class="menu-list">
{{ $slot }}
</ul>
</nav>Child Component - MenuItem
{{-- resources/views/components/menu-item.blade.php --}}
@aware([
'color' => 'gray',
'size' => 'md'
])
@props([
'href' => '#',
'active' => false
])
<li>
<a
href="{{ $href }}"
{{ $attributes->class([
"menu-item text-{$color}-600 hover:text-{$color}-800",
"text-sm" => $size === 'sm',
"text-base" => $size === 'md',
"text-lg" => $size === 'lg',
"font-bold border-b-2 border-{$color}-500" => $active,
]) }}
>
{{ $slot }}
</a>
</li>Usage
{{-- All menu items inherit color="blue" and size="lg" --}}
<x-menu color="blue" size="lg">
<x-menu-item href="/" active>Home</x-menu-item>
<x-menu-item href="/about">About</x-menu-item>
<x-menu-item href="/contact">Contact</x-menu-item>
</x-menu>@aware - Accordion Pattern
Accordion Parent
{{-- resources/views/components/accordion.blade.php --}}
@props([
'multiple' => false,
'defaultOpen' => null
])
<div
x-data="{
active: {{ $defaultOpen ? "'{$defaultOpen}'" : 'null' }},
multiple: {{ $multiple ? 'true' : 'false' }},
toggle(id) {
if (this.multiple) {
this.active = this.active === id ? null : id;
} else {
this.active = this.active === id ? null : id;
}
}
}"
{{ $attributes->merge(['class' => 'accordion divide-y']) }}
>
{{ $slot }}
</div>Accordion Item (uses @aware)
{{-- resources/views/components/accordion-item.blade.php --}}
@aware(['multiple' => false])
@props([
'id',
'title'
])
<div class="accordion-item">
<button
@click="toggle('{{ $id }}')"
class="w-full py-4 text-left font-medium flex justify-between"
:class="{ 'text-blue-600': active === '{{ $id }}' }"
>
{{ $title }}
<span x-text="active === '{{ $id }}' ? '−' : '+'"></span>
</button>
<div
x-show="active === '{{ $id }}'"
x-collapse
class="pb-4"
>
{{ $slot }}
</div>
</div>Usage
<x-accordion default-open="faq-1">
<x-accordion-item id="faq-1" title="What is your return policy?">
We offer 30-day returns on all items.
</x-accordion-item>
<x-accordion-item id="faq-2" title="How long does shipping take?">
Standard shipping takes 5-7 business days.
</x-accordion-item>
</x-accordion>shouldRender() - Conditional Rendering
Alert Component
<?php
// app/View/Components/Alert.php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\Contracts\View\View;
class Alert extends Component
{
public function __construct(
public ?string $message = null,
public string $type = 'info'
) {}
/**
* Component only renders if message exists
*/
public function shouldRender(): bool
{
return $this->message !== null && $this->message !== '';
}
public function render(): View
{
return view('components.alert');
}
public function typeClasses(): string
{
return match($this->type) {
'success' => 'bg-green-100 text-green-800 border-green-300',
'error' => 'bg-red-100 text-red-800 border-red-300',
'warning' => 'bg-yellow-100 text-yellow-800 border-yellow-300',
default => 'bg-blue-100 text-blue-800 border-blue-300',
};
}
}{{-- resources/views/components/alert.blade.php --}}
<div {{ $attributes->merge(['class' => "p-4 rounded border {$typeClasses()}"]) }}>
{{ $message }}
</div>Usage
{{-- Renders alert --}}
<x-alert message="Operation successful!" type="success" />
{{-- Does NOT render (no HTML output at all) --}}
<x-alert :message="null" />
<x-alert message="" />Feature Flag Component
<?php
// app/View/Components/Feature.php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\Contracts\View\View;
class Feature extends Component
{
public function __construct(
public string $flag
) {}
public function shouldRender(): bool
{
return config("features.{$this->flag}", false);
}
public function render(): View
{
return view('components.feature');
}
}{{-- resources/views/components/feature.blade.php --}}
{{ $slot }}Usage
{{-- Only renders if features.ai-chat is true in config --}}
<x-feature flag="ai-chat">
<x-ai-chat-widget />
</x-feature>Index Components
Directory Structure
resources/views/components/
├── card/
│ ├── index.blade.php → <x-card>
│ ├── header.blade.php → <x-card.header>
│ ├── body.blade.php → <x-card.body>
│ └── footer.blade.php → <x-card.footer>
├── form/
│ ├── index.blade.php → <x-form>
│ ├── input.blade.php → <x-form.input>
│ ├── select.blade.php → <x-form.select>
│ └── button.blade.php → <x-form.button>Card Index Component
{{-- resources/views/components/card/index.blade.php --}}
@props([
'padding' => true
])
<div {{ $attributes->merge(['class' => 'bg-white rounded-lg shadow']) }}>
@if(isset($header))
<x-card.header>{{ $header }}</x-card.header>
@endif
<x-card.body :padding="$padding">
{{ $slot }}
</x-card.body>
@if(isset($footer))
<x-card.footer>{{ $footer }}</x-card.footer>
@endif
</div>{{-- resources/views/components/card/header.blade.php --}}
<div {{ $attributes->merge(['class' => 'px-6 py-4 border-b']) }}>
{{ $slot }}
</div>{{-- resources/views/components/card/body.blade.php --}}
@props(['padding' => true])
<div {{ $attributes->merge(['class' => $padding ? 'p-6' : '']) }}>
{{ $slot }}
</div>{{-- resources/views/components/card/footer.blade.php --}}
<div {{ $attributes->merge(['class' => 'px-6 py-4 border-t bg-gray-50']) }}>
{{ $slot }}
</div>Usage
{{-- Simple card --}}
<x-card>
Content here
</x-card>
{{-- Card with header and footer --}}
<x-card>
<x-slot:header>
<h3 class="text-lg font-semibold">Card Title</h3>
</x-slot>
<p>Card content goes here.</p>
<x-slot:footer>
<x-button>Save</x-button>
</x-slot>
</x-card>
{{-- Using sub-components directly --}}
<div class="custom-card">
<x-card.header>Custom Header</x-card.header>
<x-card.body :padding="false">
<img src="/image.jpg" class="w-full">
</x-card.body>
</div>Component Namespacing (Packages)
Register in Service Provider
<?php
// packages/my-ui/src/MyUiServiceProvider.php
namespace MyVendor\MyUi;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class MyUiServiceProvider extends ServiceProvider
{
public function boot(): void
{
// Register anonymous components
Blade::anonymousComponentPath(
__DIR__ . '/../resources/views/components',
'myui'
);
// Or register class-based components
Blade::componentNamespace(
'MyVendor\\MyUi\\View\\Components',
'myui'
);
}
}Package Component
{{-- packages/my-ui/resources/views/components/button.blade.php --}}
@props([
'variant' => 'primary',
'size' => 'md'
])
<button {{ $attributes->merge([
'class' => "myui-btn myui-btn-{$variant} myui-btn-{$size}"
]) }}>
{{ $slot }}
</button>Usage in App
{{-- Using package component with namespace --}}
<x-myui::button variant="secondary">
Click Me
</x-myui::button>
<x-myui::card>
<x-myui::badge>New</x-myui::badge>
Package UI content
</x-myui::card>Scoped Slots - Passing Data Back
{{-- resources/views/components/data-list.blade.php --}}
@props(['items'])
<ul {{ $attributes->merge(['class' => 'divide-y']) }}>
@foreach($items as $index => $item)
<li class="py-2">
{{-- Pass data back to parent through scoped slot --}}
{{ $slot($item, $index, $loop) }}
</li>
@endforeach
</ul>Usage with Scoped Data
<x-data-list :items="$users">
@scope($user, $index, $loop)
<div class="flex justify-between">
<span>{{ $index + 1 }}. {{ $user->name }}</span>
<span class="text-gray-500">{{ $user->email }}</span>
@if($loop->first)
<x-badge>First</x-badge>
@endif
</div>
@endscope
</x-data-list>Advanced Directives - Complete Examples
@use - Class Imports
{{-- resources/views/pages/dashboard.blade.php --}}
@use('App\Models\User')
@use('App\Models\Order')
@use('App\Enums\OrderStatus')
<div class="stats">
<div class="stat">
<span class="label">Total Users</span>
<span class="value">{{ User::count() }}</span>
</div>
<div class="stat">
<span class="label">Pending Orders</span>
<span class="value">{{ Order::where('status', OrderStatus::Pending)->count() }}</span>
</div>
</div>@inject - Service Injection
{{-- resources/views/pages/analytics.blade.php --}}
@inject('metrics', 'App\Services\MetricsService')
@inject('carbon', 'Carbon\Carbon')
<div class="analytics">
<h2>Monthly Report - {{ $carbon::now()->format('F Y') }}</h2>
<div class="metrics">
<div class="metric">
<span>Revenue</span>
<span>${{ number_format($metrics->monthlyRevenue(), 2) }}</span>
</div>
<div class="metric">
<span>New Users</span>
<span>{{ $metrics->newUsersThisMonth() }}</span>
</div>
<div class="metric">
<span>Conversion Rate</span>
<span>{{ $metrics->conversionRate() }}%</span>
</div>
</div>
</div>@once - Prevent Script Duplication
{{-- resources/views/components/chart.blade.php --}}
@props(['data', 'type' => 'bar'])
@once
@push('styles')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.min.css">
@endpush
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
<script>
window.initCharts = function() {
document.querySelectorAll('[data-chart]').forEach(el => {
new Chart(el, JSON.parse(el.dataset.chart));
});
};
</script>
@endpush
@endonce
<canvas data-chart='@json(['type' => $type, 'data' => $data])'></canvas>
{{-- Usage: Component can be used multiple times, scripts load once --}}
{{--
<x-chart :data="$salesData" type="line" />
<x-chart :data="$usersData" type="bar" />
<x-chart :data="$revenueData" type="pie" />
--}}@switch - Role-Based UI
{{-- resources/views/pages/dashboard.blade.php --}}
<x-layouts.app>
<h1>Dashboard</h1>
@switch(auth()->user()->role)
@case('super-admin')
<x-dashboard.super-admin />
@break
@case('admin')
<x-dashboard.admin />
@break
@case('editor')
<x-dashboard.editor />
@break
@case('moderator')
<x-dashboard.moderator />
@break
@default
<x-dashboard.user />
@endswitch
</x-layouts.app>@verbatim - Vue.js Integration
{{-- resources/views/pages/vue-app.blade.php --}}
<x-layouts.app>
<div id="vue-app">
@verbatim
<div class="user-card">
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
<ul>
<li v-for="task in tasks" :key="task.id">
{{ task.title }} - {{ task.status }}
</li>
</ul>
<button @click="addTask">Add Task</button>
</div>
@endverbatim
</div>
@push('scripts')
<script type="module">
import { createApp } from 'vue';
createApp({
data() {
return {
user: @json($user),
tasks: @json($tasks)
};
},
methods: {
addTask() {
// Add task logic
}
}
}).mount('#vue-app');
</script>
@endpush
</x-layouts.app>Advanced Stacks
{{-- resources/views/components/layouts/app.blade.php --}}
<!DOCTYPE html>
<html>
<head>
<title>{{ $title ?? config('app.name') }}</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
{{-- Check if stack has content before rendering wrapper --}}
@hasStack('styles')
<style>
@stack('styles')
</style>
@endif
</head>
<body>
{{ $slot }}
@hasStack('scripts')
@stack('scripts')
@endif
@hasStack('modals')
<div class="modals-container">
@stack('modals')
</div>
@endif
</body>
</html>
{{-- resources/views/pages/product.blade.php --}}
<x-layouts.app title="Product Details">
<h1>{{ $product->name }}</h1>
{{-- Conditional push based on product type --}}
@pushIf($product->has_gallery, 'scripts')
<script src="/js/lightbox.js"></script>
@endPushIf
{{-- Prepend critical styles --}}
@prepend('styles')
.product-hero { background: var(--primary); }
@endprepend
{{-- Push modal to dedicated stack --}}
@push('modals')
<x-modal id="quick-view">
<x-product.quick-view :product="$product" />
</x-modal>
@endpush
<x-product.details :product="$product" />
</x-layouts.app>Environment Directives
{{-- resources/views/components/layouts/app.blade.php --}}
<head>
{{-- Production-only analytics --}}
@production
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_ID"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_ID');
</script>
@endproduction
{{-- Development tools --}}
@env('local')
<script src="http://localhost:3000/browser-sync/browser-sync-client.js"></script>
@endenv
{{-- Staging/Local debug --}}
@env(['local', 'staging'])
<style>
.debug-bar { display: block; }
</style>
@endenv
</head>
<body>
{{-- Environment banner --}}
@env('staging')
<div class="env-banner bg-yellow-500 text-center py-2">
STAGING ENVIRONMENT - Data may be reset
</div>
@endenv
{{ $slot }}
</body>@session - Flash Messages
{{-- resources/views/components/layouts/app.blade.php --}}
<main>
{{-- Success message --}}
@session('success')
<div class="alert alert-success" role="alert">
{{ session('success') }}
</div>
@endsession
{{-- Error message --}}
@session('error')
<div class="alert alert-danger" role="alert">
{{ session('error') }}
</div>
@endsession
{{-- Status message (generic) --}}
@session('status')
<div class="alert alert-info" role="alert">
{{ session('status') }}
</div>
@endsession
{{ $slot }}
</main>Combined Example - Complete Page
{{-- resources/views/pages/admin/users.blade.php --}}
@use('App\Models\User')
@use('App\Enums\UserRole')
@inject('permissions', 'App\Services\PermissionService')
<x-layouts.admin title="User Management">
@session('user-updated')
<x-alert type="success">{{ session('user-updated') }}</x-alert>
@endsession
<div class="users-page">
<header class="flex justify-between">
<h1>Users ({{ User::count() }})</h1>
@can('create', User::class)
<x-button href="{{ route('admin.users.create') }}">
Add User
</x-button>
@endcan
</header>
@forelse($users as $user)
<x-card class="user-card">
<x-slot:header>
{{ $user->name }}
@switch($user->role)
@case(UserRole::Admin)
<x-badge color="red">Admin</x-badge>
@break
@case(UserRole::Editor)
<x-badge color="blue">Editor</x-badge>
@break
@default
<x-badge color="gray">User</x-badge>
@endswitch
</x-slot>
<p>{{ $user->email }}</p>
<x-slot:footer>
@can('update', $user)
<x-button size="sm" href="{{ route('admin.users.edit', $user) }}">
Edit
</x-button>
@endcan
</x-slot>
</x-card>
@once
@push('scripts')
<script src="/js/user-actions.js"></script>
@endpush
@endonce
@empty
<x-empty-state message="No users found" />
@endforelse
</div>
@env('local')
<x-debug :data="['users' => $users->count(), 'permissions' => $permissions->all()]" />
@endenv
</x-layouts.admin>Anonymous Component
Button Component
{{-- resources/views/components/button.blade.php --}}
@props([
'type' => 'button',
'variant' => 'primary',
'size' => 'md',
'disabled' => false,
'href' => null,
])
@php
$baseClasses = 'inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2';
$variants = [
'primary' => 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',
'secondary' => 'bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500',
'danger' => 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
'ghost' => 'bg-transparent text-gray-700 hover:bg-gray-100 focus:ring-gray-500',
];
$sizes = [
'sm' => 'px-3 py-1.5 text-sm',
'md' => 'px-4 py-2 text-base',
'lg' => 'px-6 py-3 text-lg',
];
$classes = $baseClasses . ' ' . $variants[$variant] . ' ' . $sizes[$size];
@endphp
@if ($href)
<a
href="{{ $href }}"
{{ $attributes->merge(['class' => $classes]) }}
>
{{ $slot }}
</a>
@else
<button
type="{{ $type }}"
{{ $attributes->merge(['class' => $classes])->class([
'opacity-50 cursor-not-allowed' => $disabled
]) }}
@disabled($disabled)
>
{{ $slot }}
</button>
@endifInput Component
{{-- resources/views/components/input.blade.php --}}
@props([
'type' => 'text',
'name' => '',
'label' => null,
'error' => null,
'hint' => null,
])
@php
$hasError = $error || $errors->has($name);
$errorMessage = $error ?? $errors->first($name);
@endphp
<div {{ $attributes->only(['class'])->merge(['class' => 'space-y-1']) }}>
@if ($label)
<label for="{{ $name }}" class="block text-sm font-medium text-gray-700">
{{ $label }}
</label>
@endif
<input
type="{{ $type }}"
name="{{ $name }}"
id="{{ $name }}"
value="{{ old($name, $slot) }}"
{{ $attributes->except(['class'])->merge([
'class' => 'block w-full rounded-md shadow-sm sm:text-sm ' .
($hasError
? 'border-red-300 text-red-900 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500')
]) }}
>
@if ($hasError)
<p class="text-sm text-red-600">{{ $errorMessage }}</p>
@elseif ($hint)
<p class="text-sm text-gray-500">{{ $hint }}</p>
@endif
</div>Badge Component
{{-- resources/views/components/badge.blade.php --}}
@props([
'color' => 'gray',
'size' => 'md',
])
@php
$colors = [
'gray' => 'bg-gray-100 text-gray-800',
'red' => 'bg-red-100 text-red-800',
'green' => 'bg-green-100 text-green-800',
'blue' => 'bg-blue-100 text-blue-800',
'yellow' => 'bg-yellow-100 text-yellow-800',
];
$sizes = [
'sm' => 'px-2 py-0.5 text-xs',
'md' => 'px-2.5 py-0.5 text-sm',
'lg' => 'px-3 py-1 text-base',
];
@endphp
<span {{ $attributes->merge([
'class' => 'inline-flex items-center font-medium rounded-full ' . $colors[$color] . ' ' . $sizes[$size]
]) }}>
{{ $slot }}
</span>Usage Examples
{{-- Buttons --}}
<x-button>Default</x-button>
<x-button variant="secondary">Secondary</x-button>
<x-button variant="danger" size="lg">Delete</x-button>
<x-button href="/dashboard">Go to Dashboard</x-button>
<x-button :disabled="$isProcessing">
@if ($isProcessing)
Processing...
@else
Submit
@endif
</x-button>
{{-- Inputs --}}
<x-input name="email" type="email" label="Email Address" hint="We'll never share your email" />
<x-input name="password" type="password" label="Password" :error="$passwordError" />
{{-- Badges --}}
<x-badge color="green">Active</x-badge>
<x-badge color="red" size="sm">Urgent</x-badge>Artisan Command
# Create anonymous component (view only)
php artisan make:component button --viewCard with Named Slots
Card Component
{{-- resources/views/components/card.blade.php --}}
@props([
'padding' => true,
'shadow' => 'md',
'rounded' => 'lg',
])
@php
$shadows = [
'none' => '',
'sm' => 'shadow-sm',
'md' => 'shadow-md',
'lg' => 'shadow-lg',
'xl' => 'shadow-xl',
];
$roundings = [
'none' => '',
'sm' => 'rounded-sm',
'md' => 'rounded-md',
'lg' => 'rounded-lg',
'xl' => 'rounded-xl',
'full' => 'rounded-2xl',
];
@endphp
<div {{ $attributes->merge([
'class' => 'bg-white overflow-hidden ' . $shadows[$shadow] . ' ' . $roundings[$rounded]
]) }}>
{{-- Header --}}
@if (isset($header))
<div {{ $header->attributes->class(['px-4 py-5 sm:px-6 border-b border-gray-200']) }}>
{{ $header }}
</div>
@endif
{{-- Image --}}
@if (isset($image))
<div {{ $image->attributes->class(['relative']) }}>
{{ $image }}
</div>
@endif
{{-- Body --}}
<div @class([
'px-4 py-5 sm:p-6' => $padding,
])>
{{ $slot }}
</div>
{{-- Footer --}}
@if (isset($footer))
<div {{ $footer->attributes->class(['px-4 py-4 sm:px-6 border-t border-gray-200 bg-gray-50']) }}>
{{ $footer }}
</div>
@endif
</div>Basic Usage
<x-card>
<p>Simple card with just content.</p>
</x-card>With Header and Footer
<x-card>
<x-slot:header class="flex justify-between items-center">
<h3 class="text-lg font-medium text-gray-900">Card Title</h3>
<x-badge color="green">Active</x-badge>
</x-slot>
<p class="text-gray-600">
This is the main content of the card.
</p>
<x-slot:footer class="flex justify-end space-x-3">
<x-button variant="ghost" size="sm">Cancel</x-button>
<x-button size="sm">Save</x-button>
</x-slot>
</x-card>With Image
<x-card :padding="false" class="max-w-sm">
<x-slot:image>
<img
src="{{ $product->image_url }}"
alt="{{ $product->name }}"
class="w-full h-48 object-cover"
>
@if ($product->is_featured)
<span class="absolute top-2 right-2">
<x-badge color="yellow">Featured</x-badge>
</span>
@endif
</x-slot>
<div class="p-4">
<h3 class="font-semibold text-gray-900">{{ $product->name }}</h3>
<p class="text-gray-600 text-sm mt-1">{{ $product->description }}</p>
<p class="text-lg font-bold text-blue-600 mt-2">${{ $product->price }}</p>
</div>
<x-slot:footer class="p-4 pt-0">
<x-button class="w-full">Add to Cart</x-button>
</x-slot>
</x-card>Stats Card
{{-- resources/views/components/stats-card.blade.php --}}
@props([
'title',
'value',
'change' => null,
'changeType' => 'neutral', // positive, negative, neutral
])
<x-card>
<x-slot:header class="pb-2">
<h3 class="text-sm font-medium text-gray-500">{{ $title }}</h3>
</x-slot>
<div class="flex items-baseline">
<p class="text-3xl font-semibold text-gray-900">{{ $value }}</p>
@if ($change)
<p @class([
'ml-2 flex items-baseline text-sm font-semibold',
'text-green-600' => $changeType === 'positive',
'text-red-600' => $changeType === 'negative',
'text-gray-500' => $changeType === 'neutral',
])>
@if ($changeType === 'positive')
<x-heroicon-s-arrow-up class="h-4 w-4 flex-shrink-0 self-center" />
@elseif ($changeType === 'negative')
<x-heroicon-s-arrow-down class="h-4 w-4 flex-shrink-0 self-center" />
@endif
{{ $change }}
</p>
@endif
</div>
@if ($slot->isNotEmpty())
<div class="mt-4">
{{ $slot }}
</div>
@endif
</x-card>Stats Card Usage
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
<x-stats-card
title="Total Revenue"
value="$45,231.89"
change="+20.1%"
change-type="positive"
/>
<x-stats-card
title="Active Users"
value="2,350"
change="-4.5%"
change-type="negative"
>
<a href="{{ route('users.index') }}" class="text-sm text-blue-600 hover:underline">
View all users →
</a>
</x-stats-card>
<x-stats-card
title="Pending Orders"
value="12"
/>
</div>Modal Card
{{-- resources/views/components/modal.blade.php --}}
@props([
'name',
'maxWidth' => 'md',
])
@php
$maxWidths = [
'sm' => 'sm:max-w-sm',
'md' => 'sm:max-w-md',
'lg' => 'sm:max-w-lg',
'xl' => 'sm:max-w-xl',
'2xl' => 'sm:max-w-2xl',
];
@endphp
<div
x-data="{ show: false }"
x-on:open-modal.window="if ($event.detail === '{{ $name }}') show = true"
x-on:close-modal.window="if ($event.detail === '{{ $name }}') show = false"
x-on:keydown.escape.window="show = false"
x-show="show"
x-cloak
class="fixed inset-0 z-50 overflow-y-auto"
>
{{-- Backdrop --}}
<div
x-show="show"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 bg-gray-500 bg-opacity-75"
@click="show = false"
></div>
{{-- Modal --}}
<div class="flex min-h-full items-center justify-center p-4">
<div
x-show="show"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
{{ $attributes->merge(['class' => 'relative w-full ' . $maxWidths[$maxWidth]]) }}
>
<x-card>
@if (isset($header))
<x-slot:header class="flex justify-between items-center">
{{ $header }}
<button @click="show = false" class="text-gray-400 hover:text-gray-500">
<x-heroicon-o-x-mark class="h-5 w-5" />
</button>
</x-slot>
@endif
{{ $slot }}
@if (isset($footer))
<x-slot:footer>
{{ $footer }}
</x-slot>
@endif
</x-card>
</div>
</div>
</div>Class-based Component
Component Class
<?php
declare(strict_types=1);
namespace App\View\Components;
use App\Services\NotificationService;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class Alert extends Component
{
public function __construct(
public string $type = 'info',
public string $message = '',
public bool $dismissible = true,
private NotificationService $notifications = new NotificationService()
) {}
/**
* Computed property - accessible in view as $alertClasses
*/
public function alertClasses(): string
{
return match ($this->type) {
'success' => 'bg-green-100 border-green-500 text-green-700',
'warning' => 'bg-yellow-100 border-yellow-500 text-yellow-700',
'error' => 'bg-red-100 border-red-500 text-red-700',
default => 'bg-blue-100 border-blue-500 text-blue-700',
};
}
/**
* Computed property - accessible as $icon
*/
public function icon(): string
{
return match ($this->type) {
'success' => 'check-circle',
'warning' => 'exclamation-triangle',
'error' => 'x-circle',
default => 'information-circle',
};
}
/**
* Control rendering
*/
public function shouldRender(): bool
{
return !empty($this->message);
}
public function render(): View
{
return view('components.alert');
}
}Component View
{{-- resources/views/components/alert.blade.php --}}
<div
{{ $attributes->merge([
'class' => 'border-l-4 p-4 rounded ' . $alertClasses(),
'role' => 'alert'
]) }}
x-data="{ open: true }"
x-show="open"
>
<div class="flex items-start">
<x-dynamic-component :component="'heroicon-o-' . $icon()" class="h-5 w-5 mr-3 flex-shrink-0" />
<div class="flex-1">
{{ $message }}
{{ $slot }}
</div>
@if ($dismissible)
<button
type="button"
class="ml-4 text-current opacity-50 hover:opacity-100"
@click="open = false"
>
<x-heroicon-o-x-mark class="h-5 w-5" />
</button>
@endif
</div>
</div>Usage
{{-- Basic usage --}}
<x-alert type="success" message="Operation completed!" />
{{-- With additional content --}}
<x-alert type="error" message="Failed to save">
<p class="mt-2 text-sm">Please try again or contact support.</p>
</x-alert>
{{-- With attributes --}}
<x-alert
type="warning"
message="Session expiring"
:dismissible="false"
class="mt-4"
id="session-alert"
/>
{{-- Dynamic type --}}
<x-alert :type="$notification->level" :message="$notification->text" />With Service Injection
<?php
declare(strict_types=1);
namespace App\View\Components;
use App\Models\User;
use App\Repositories\UserRepository;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class UserCard extends Component
{
public User $user;
public function __construct(
int $userId,
private UserRepository $users
) {
$this->user = $this->users->findOrFail($userId);
}
public function avatarUrl(): string
{
return $this->user->avatar_url
?? 'https://ui-avatars.com/api/?name=' . urlencode($this->user->name);
}
public function render(): View
{
return view('components.user-card');
}
}Artisan Command
# Create class component
php artisan make:component Alert
# Create in subdirectory
php artisan make:component Forms/Input
# Create inline (no view)
php artisan make:component Alert --inlineCustom Directives - Complete Examples
Service Provider Setup
<?php
// app/Providers/BladeServiceProvider.php
namespace App\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class BladeServiceProvider extends ServiceProvider
{
public function boot(): void
{
$this->registerCustomConditionals();
$this->registerCustomDirectives();
}
private function registerCustomConditionals(): void
{
// @admin / @endadmin
Blade::if('admin', function () {
return auth()->check() && auth()->user()->isAdmin();
});
// @role('editor') / @endrole
Blade::if('role', function (string $role) {
return auth()->check() && auth()->user()->hasRole($role);
});
// @subscribed('pro') / @endsubscribed
Blade::if('subscribed', function (string $plan = null) {
if (!auth()->check()) {
return false;
}
return $plan
? auth()->user()->subscribedToPlan($plan)
: auth()->user()->subscribed();
});
// @feature('dark-mode') / @endfeature
Blade::if('feature', function (string $feature) {
return config("features.{$feature}", false);
});
// @disk('s3') / @elsedisk('local') / @enddisk
Blade::if('disk', function (string $driver) {
return config('filesystems.default') === $driver;
});
// @locale('en') / @endlocale
Blade::if('locale', function (string $locale) {
return app()->getLocale() === $locale;
});
// @impersonating / @endimpersonating
Blade::if('impersonating', function () {
return session()->has('impersonator_id');
});
}
private function registerCustomDirectives(): void
{
// @datetime($date) - Format Carbon date
Blade::directive('datetime', function (string $expression) {
return "<?php echo ($expression)->format('M j, Y g:i A'); ?>";
});
// @date($date) - Format date only
Blade::directive('date', function (string $expression) {
return "<?php echo ($expression)->format('M j, Y'); ?>";
});
// @time($date) - Format time only
Blade::directive('time', function (string $expression) {
return "<?php echo ($expression)->format('g:i A'); ?>";
});
// @money($amount, 'USD') - Format currency
Blade::directive('money', function (string $expression) {
return "<?php echo \App\Helpers\MoneyHelper::format($expression); ?>";
});
// @markdown($content) - Render markdown
Blade::directive('markdown', function (string $expression) {
return "<?php echo \Illuminate\Support\Str::markdown($expression); ?>";
});
// @nl2br($text) - Newlines to <br>
Blade::directive('nl2br', function (string $expression) {
return "<?php echo nl2br(e($expression)); ?>";
});
// @truncate($text, 100) - Truncate with ellipsis
Blade::directive('truncate', function (string $expression) {
return "<?php echo \Illuminate\Support\Str::limit($expression); ?>";
});
// @route('name', ['param' => 'value']) - Generate URL
Blade::directive('routeIs', function (string $expression) {
return "<?php if(request()->routeIs($expression)): ?>";
});
// @endrouteIs
Blade::directive('endrouteIs', function () {
return '<?php endif; ?>';
});
}
}Register Service Provider
<?php
// bootstrap/providers.php
return [
App\Providers\AppServiceProvider::class,
App\Providers\BladeServiceProvider::class, // Add this
];Money Helper
<?php
// app/Helpers/MoneyHelper.php
namespace App\Helpers;
use NumberFormatter;
class MoneyHelper
{
public static function format(
float $amount,
string $currency = 'USD',
string $locale = null
): string {
$locale = $locale ?? app()->getLocale();
$formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY);
return $formatter->formatCurrency($amount, $currency);
}
}Usage Examples
{{-- Custom Conditionals --}}
{{-- @admin --}}
@admin
<a href="{{ route('admin.dashboard') }}">Admin Panel</a>
@endadmin
{{-- @role with @elserole --}}
@role('super-admin')
<x-super-admin-tools />
@elserole('admin')
<x-admin-tools />
@elserole('editor')
<x-editor-tools />
@endrole
{{-- @subscribed --}}
@subscribed
<x-premium-content />
@else
<x-upgrade-prompt />
@endsubscribed
{{-- @subscribed with plan --}}
@subscribed('enterprise')
<x-enterprise-features />
@endsubscribed
{{-- @feature flag --}}
@feature('dark-mode')
<x-dark-mode-toggle />
@endfeature
{{-- @disk --}}
@disk('s3')
<p>Files are stored on Amazon S3</p>
@elsedisk('local')
<p>Files are stored locally</p>
@enddisk
{{-- @locale --}}
@locale('fr')
<p>Bienvenue sur notre site</p>
@endlocale
@locale('en')
<p>Welcome to our site</p>
@endlocale
{{-- @impersonating --}}
@impersonating
<div class="impersonation-banner">
You are impersonating {{ auth()->user()->name }}.
<a href="{{ route('impersonate.stop') }}">Stop</a>
</div>
@endimpersonating
{{-- Custom Directives --}}
{{-- @datetime --}}
<p>Created: @datetime($post->created_at)</p>
{{-- Output: Created: Jan 15, 2024 3:30 PM --}}
{{-- @date --}}
<p>Published: @date($post->published_at)</p>
{{-- Output: Published: Jan 15, 2024 --}}
{{-- @money --}}
<p>Price: @money($product->price, 'EUR')</p>
{{-- Output: Price: €29.99 --}}
{{-- @markdown --}}
<div class="prose">
@markdown($post->content)
</div>
{{-- @truncate --}}
<p>@truncate($post->excerpt, 150)</p>
{{-- Output: First 150 characters... --}}
{{-- @nl2br --}}
<p>@nl2br($comment->body)</p>
{{-- Converts newlines to <br> tags --}}
{{-- @routeIs --}}
<nav>
<a href="/" @routeIs('home') class="active" @endrouteIs>Home</a>
<a href="/about" @routeIs('about') class="active" @endrouteIs>About</a>
</nav>Testing Custom Directives
<?php
// tests/Feature/BladeDirectivesTest.php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Support\Facades\Blade;
use Tests\TestCase;
class BladeDirectivesTest extends TestCase
{
public function test_admin_directive_shows_for_admins(): void
{
$admin = User::factory()->admin()->create();
$this->actingAs($admin);
$result = Blade::render('@admin ADMIN CONTENT @endadmin');
$this->assertStringContainsString('ADMIN CONTENT', $result);
}
public function test_admin_directive_hides_for_users(): void
{
$user = User::factory()->create();
$this->actingAs($user);
$result = Blade::render('@admin ADMIN CONTENT @endadmin');
$this->assertStringNotContainsString('ADMIN CONTENT', $result);
}
public function test_money_directive_formats_currency(): void
{
$result = Blade::render('@money(29.99, "USD")');
$this->assertStringContainsString('$29.99', $result);
}
public function test_datetime_directive_formats_date(): void
{
$date = now()->setDate(2024, 1, 15)->setTime(15, 30);
$result = Blade::render(
'@datetime($date)',
['date' => $date]
);
$this->assertStringContainsString('Jan 15, 2024', $result);
$this->assertStringContainsString('3:30 PM', $result);
}
}Dynamic Component
Basic Usage
{{-- Render component based on variable --}}
<x-dynamic-component :component="$componentName" />
{{-- With props --}}
<x-dynamic-component
:component="$alertType . '-alert'"
:message="$message"
/>Widget System
{{-- resources/views/dashboard.blade.php --}}
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
@foreach ($widgets as $widget)
<x-dynamic-component
:component="'widgets.' . $widget->type"
:data="$widget->data"
:title="$widget->title"
/>
@endforeach
</div>Widget Components
{{-- resources/views/components/widgets/chart.blade.php --}}
@props(['title', 'data'])
<x-card>
<x-slot:header>{{ $title }}</x-slot>
<div
x-data="chartWidget(@js($data))"
x-init="init()"
>
<canvas x-ref="canvas"></canvas>
</div>
</x-card>{{-- resources/views/components/widgets/stats.blade.php --}}
@props(['title', 'data'])
<x-card>
<x-slot:header>{{ $title }}</x-slot>
<div class="grid grid-cols-2 gap-4">
@foreach ($data['items'] as $stat)
<div>
<p class="text-sm text-gray-500">{{ $stat['label'] }}</p>
<p class="text-2xl font-semibold">{{ $stat['value'] }}</p>
</div>
@endforeach
</div>
</x-card>{{-- resources/views/components/widgets/recent-activity.blade.php --}}
@props(['title', 'data'])
<x-card>
<x-slot:header>{{ $title }}</x-slot>
<ul class="divide-y divide-gray-200">
@foreach ($data['activities'] as $activity)
<li class="py-3">
<div class="flex items-center space-x-3">
<x-avatar :user="$activity->user" size="sm" />
<div>
<p class="text-sm text-gray-900">{{ $activity->description }}</p>
<p class="text-xs text-gray-500">{{ $activity->created_at->diffForHumans() }}</p>
</div>
</div>
</li>
@endforeach
</ul>
</x-card>Form Field Factory
{{-- resources/views/components/form-field.blade.php --}}
@props([
'field', // Object with: type, name, label, options, rules
])
@php
$componentMap = [
'text' => 'input',
'email' => 'input',
'password' => 'input',
'textarea' => 'textarea',
'select' => 'select',
'checkbox' => 'checkbox',
'radio' => 'radio-group',
'file' => 'file-input',
'date' => 'date-picker',
'rich-text' => 'rich-editor',
];
$component = $componentMap[$field->type] ?? 'input';
@endphp
<div>
@if ($field->label && !in_array($field->type, ['checkbox']))
<x-label :for="$field->name" :required="$field->isRequired()">
{{ $field->label }}
</x-label>
@endif
<x-dynamic-component
:component="$component"
:type="$field->type"
:name="$field->name"
:value="old($field->name, $field->value)"
:options="$field->options ?? []"
:placeholder="$field->placeholder ?? ''"
{{ $attributes }}
/>
@if ($field->hint)
<p class="mt-1 text-sm text-gray-500">{{ $field->hint }}</p>
@endif
<x-error :name="$field->name" />
</div>Form Field Usage
<x-form action="{{ route('users.store') }}">
@foreach ($formSchema as $field)
<x-form-field :field="$field" />
@endforeach
<x-button type="submit">Submit</x-button>
</x-form>Icon Component
{{-- resources/views/components/icon.blade.php --}}
@props([
'name',
'type' => 'outline', // outline, solid, mini
])
@php
$prefix = match ($type) {
'solid' => 'heroicon-s',
'mini' => 'heroicon-m',
default => 'heroicon-o',
};
@endphp
<x-dynamic-component
:component="$prefix . '-' . $name"
{{ $attributes }}
/>Icon Usage
<x-icon name="user" class="h-5 w-5" />
<x-icon name="check" type="solid" class="h-5 w-5 text-green-500" />
<x-icon name="x-mark" type="mini" class="h-4 w-4" />Polymorphic Content Blocks
{{-- resources/views/page.blade.php --}}
@foreach ($page->blocks as $block)
<x-dynamic-component
:component="'blocks.' . $block->type"
:content="$block->content"
:settings="$block->settings"
/>
@endforeachBlock Components
{{-- resources/views/components/blocks/hero.blade.php --}}
@props(['content', 'settings'])
<section @class([
'py-20 px-4',
'bg-gray-900 text-white' => $settings['dark'] ?? false,
'bg-white' => !($settings['dark'] ?? false),
])>
<div class="max-w-4xl mx-auto text-center">
<h1 class="text-4xl font-bold">{{ $content['title'] }}</h1>
<p class="mt-4 text-xl">{{ $content['subtitle'] }}</p>
@if ($content['cta'] ?? null)
<x-button :href="$content['cta']['url']" class="mt-8">
{{ $content['cta']['text'] }}
</x-button>
@endif
</div>
</section>{{-- resources/views/components/blocks/features.blade.php --}}
@props(['content', 'settings'])
<section class="py-16 px-4">
<div class="max-w-6xl mx-auto">
<h2 class="text-3xl font-bold text-center mb-12">{{ $content['title'] }}</h2>
<div class="grid grid-cols-1 md:grid-cols-{{ $settings['columns'] ?? 3 }} gap-8">
@foreach ($content['features'] as $feature)
<div class="text-center">
<x-icon :name="$feature['icon']" class="h-12 w-12 mx-auto text-blue-500" />
<h3 class="mt-4 text-lg font-semibold">{{ $feature['title'] }}</h3>
<p class="mt-2 text-gray-600">{{ $feature['description'] }}</p>
</div>
@endforeach
</div>
</div>
</section>Form Component
Form Wrapper
{{-- resources/views/components/form.blade.php --}}
@props([
'action' => '',
'method' => 'POST',
'hasFiles' => false,
])
@php
$realMethod = strtoupper($method);
$formMethod = in_array($realMethod, ['GET', 'POST']) ? $realMethod : 'POST';
@endphp
<form
action="{{ $action }}"
method="{{ $formMethod }}"
{{ $attributes->merge(['class' => 'space-y-6']) }}
@if ($hasFiles) enctype="multipart/form-data" @endif
>
@csrf
@if (!in_array($realMethod, ['GET', 'POST']))
@method($realMethod)
@endif
{{ $slot }}
</form>Complete Contact Form
{{-- resources/views/contact.blade.php --}}
<x-layouts.app title="Contact Us">
<div class="max-w-2xl mx-auto">
<h1 class="text-2xl font-bold mb-6">Contact Us</h1>
<x-form action="{{ route('contact.store') }}">
{{-- Name --}}
<div>
<x-label for="name" required>Name</x-label>
<x-input
name="name"
:value="old('name')"
placeholder="Your full name"
required
autofocus
/>
<x-error name="name" />
</div>
{{-- Email --}}
<div>
<x-label for="email" required>Email</x-label>
<x-input
type="email"
name="email"
:value="old('email')"
placeholder="your@email.com"
required
/>
<x-error name="email" />
</div>
{{-- Subject --}}
<div>
<x-label for="subject">Subject</x-label>
<x-select name="subject">
<option value="">Select a topic</option>
<option value="general" @selected(old('subject') === 'general')>General Inquiry</option>
<option value="support" @selected(old('subject') === 'support')>Support</option>
<option value="feedback" @selected(old('subject') === 'feedback')>Feedback</option>
</x-select>
<x-error name="subject" />
</div>
{{-- Message --}}
<div>
<x-label for="message" required>Message</x-label>
<x-textarea
name="message"
rows="5"
placeholder="How can we help you?"
required
>{{ old('message') }}</x-textarea>
<x-error name="message" />
</div>
{{-- File Upload --}}
<div>
<x-label for="attachment">Attachment (optional)</x-label>
<x-file-input name="attachment" accept=".pdf,.doc,.docx,.png,.jpg" />
<x-error name="attachment" />
<p class="text-sm text-gray-500 mt-1">Max 10MB. PDF, Word, or images.</p>
</div>
{{-- Terms --}}
<div class="flex items-start">
<x-checkbox name="terms" id="terms" required />
<x-label for="terms" class="ml-2">
I agree to the <a href="/terms" class="text-blue-600 hover:underline">Terms of Service</a>
</x-label>
</div>
<x-error name="terms" />
{{-- Submit --}}
<div class="flex items-center justify-end space-x-4">
<x-button type="button" variant="ghost" onclick="history.back()">
Cancel
</x-button>
<x-button type="submit">
Send Message
</x-button>
</div>
</x-form>
</div>
</x-layouts.app>Supporting Components
Label
{{-- resources/views/components/label.blade.php --}}
@props(['required' => false])
<label {{ $attributes->merge(['class' => 'block text-sm font-medium text-gray-700']) }}>
{{ $slot }}
@if ($required)
<span class="text-red-500">*</span>
@endif
</label>Error
{{-- resources/views/components/error.blade.php --}}
@props(['name'])
@error($name)
<p {{ $attributes->merge(['class' => 'mt-1 text-sm text-red-600']) }}>
{{ $message }}
</p>
@enderrorTextarea
{{-- resources/views/components/textarea.blade.php --}}
@props(['name', 'disabled' => false])
<textarea
name="{{ $name }}"
id="{{ $name }}"
{{ $attributes->merge([
'class' => 'block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm'
])->class([
'border-red-300 text-red-900' => $errors->has($name)
]) }}
@disabled($disabled)
>{{ $slot }}</textarea>Select
{{-- resources/views/components/select.blade.php --}}
@props(['name', 'disabled' => false])
<select
name="{{ $name }}"
id="{{ $name }}"
{{ $attributes->merge([
'class' => 'block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm'
])->class([
'border-red-300' => $errors->has($name)
]) }}
@disabled($disabled)
>
{{ $slot }}
</select>Checkbox
{{-- resources/views/components/checkbox.blade.php --}}
@props(['name' => '', 'value' => '1', 'checked' => false])
<input
type="checkbox"
name="{{ $name }}"
value="{{ $value }}"
{{ $attributes->merge(['class' => 'h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500']) }}
@checked($checked || old($name) === $value)
>File Input
{{-- resources/views/components/file-input.blade.php --}}
@props(['name'])
<input
type="file"
name="{{ $name }}"
id="{{ $name }}"
{{ $attributes->merge([
'class' => 'block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-medium file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100'
]) }}
>Fragments - Complete Examples
Basic Fragment Definition
{{-- resources/views/pages/users.blade.php --}}
<x-layouts.app title="Users">
<div class="users-page">
<header class="flex justify-between mb-6">
<h1>User Management</h1>
<x-button hx-get="{{ route('users.create-form') }}" hx-target="#modal">
Add User
</x-button>
</header>
@fragment('user-list')
<div id="user-list" class="space-y-4">
@forelse($users as $user)
<x-card class="user-card">
<div class="flex justify-between items-center">
<div>
<h3 class="font-semibold">{{ $user->name }}</h3>
<p class="text-gray-500">{{ $user->email }}</p>
</div>
<div class="flex gap-2">
<x-button
size="sm"
hx-get="{{ route('users.edit', $user) }}"
hx-target="#modal"
>
Edit
</x-button>
<x-button
size="sm"
variant="danger"
hx-delete="{{ route('users.destroy', $user) }}"
hx-target="#user-list"
hx-swap="outerHTML"
hx-confirm="Delete {{ $user->name }}?"
>
Delete
</x-button>
</div>
</div>
</x-card>
@empty
<x-empty-state message="No users found" />
@endforelse
{{ $users->links() }}
</div>
@endfragment
@fragment('user-stats')
<div id="user-stats" class="grid grid-cols-3 gap-4 mt-6">
<x-stat label="Total Users" :value="$totalUsers" />
<x-stat label="Active" :value="$activeUsers" />
<x-stat label="New This Month" :value="$newUsers" />
</div>
@endfragment
<div id="modal"></div>
</div>
</x-layouts.app>Controller with Fragments
<?php
// app/Http/Controllers/UserController.php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\View\View;
class UserController extends Controller
{
public function index(Request $request): View
{
$users = User::latest()->paginate(10);
$viewData = [
'users' => $users,
'totalUsers' => User::count(),
'activeUsers' => User::where('active', true)->count(),
'newUsers' => User::where('created_at', '>=', now()->subMonth())->count(),
];
return view('pages.users', $viewData)
->fragmentIf($request->hasHeader('HX-Request'), 'user-list');
}
public function destroy(Request $request, User $user): View
{
$user->delete();
// Return updated list fragment
return $this->index($request);
}
public function store(Request $request): View
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
]);
User::create($validated);
// Return both list and stats fragments
$viewData = [
'users' => User::latest()->paginate(10),
'totalUsers' => User::count(),
'activeUsers' => User::where('active', true)->count(),
'newUsers' => User::where('created_at', '>=', now()->subMonth())->count(),
];
return view('pages.users', $viewData)
->fragmentIf(
$request->hasHeader('HX-Request'),
['user-list', 'user-stats']
);
}
}Live Search with Fragments
{{-- resources/views/pages/products.blade.php --}}
<x-layouts.app title="Products">
<div class="products-page">
{{-- Search input triggers fragment update --}}
<input
type="search"
name="search"
placeholder="Search products..."
hx-get="{{ route('products.index') }}"
hx-trigger="input changed delay:300ms"
hx-target="#product-list"
hx-swap="outerHTML"
class="w-full px-4 py-2 border rounded"
/>
@fragment('product-list')
<div id="product-list" class="grid grid-cols-3 gap-4 mt-6">
@forelse($products as $product)
<x-product-card :product="$product" />
@empty
<div class="col-span-3">
<x-empty-state message="No products match your search" />
</div>
@endforelse
</div>
@endfragment
</div>
</x-layouts.app><?php
// Controller
public function index(Request $request): View
{
$products = Product::query()
->when($request->search, fn($q, $search) =>
$q->where('name', 'like', "%{$search}%")
)
->latest()
->paginate(12);
return view('pages.products', compact('products'))
->fragmentIf($request->hasHeader('HX-Request'), 'product-list');
}Infinite Scroll
{{-- resources/views/pages/feed.blade.php --}}
<x-layouts.app title="Feed">
@fragment('feed-items')
<div id="feed-items">
@foreach($posts as $post)
<x-post-card :post="$post" />
@endforeach
@if($posts->hasMorePages())
<div
hx-get="{{ $posts->nextPageUrl() }}"
hx-trigger="revealed"
hx-swap="afterend"
hx-select="#feed-items > *"
class="loading-indicator"
>
<x-spinner />
</div>
@endif
</div>
@endfragment
</x-layouts.app>Tab Content Loading
{{-- resources/views/pages/dashboard.blade.php --}}
<x-layouts.app title="Dashboard">
<div class="tabs">
<nav class="tab-nav">
<button
hx-get="{{ route('dashboard.overview') }}"
hx-target="#tab-content"
class="tab-btn active"
>
Overview
</button>
<button
hx-get="{{ route('dashboard.analytics') }}"
hx-target="#tab-content"
class="tab-btn"
>
Analytics
</button>
<button
hx-get="{{ route('dashboard.reports') }}"
hx-target="#tab-content"
class="tab-btn"
>
Reports
</button>
</nav>
<div id="tab-content">
@fragment('tab-content')
{{-- Initial content or loaded via HTMX --}}
@include('dashboard.partials.overview')
@endfragment
</div>
</div>
</x-layouts.app><?php
// DashboardController.php
public function overview(Request $request): View
{
return view('dashboard.overview', ['stats' => $this->getStats()])
->fragmentIf($request->hasHeader('HX-Request'), 'tab-content');
}
public function analytics(Request $request): View
{
return view('dashboard.analytics', ['data' => $this->getAnalytics()])
->fragmentIf($request->hasHeader('HX-Request'), 'tab-content');
}Form with Fragment Response
{{-- resources/views/components/contact-form.blade.php --}}
@fragment('contact-form')
<form
id="contact-form"
hx-post="{{ route('contact.store') }}"
hx-target="#contact-form"
hx-swap="outerHTML"
class="space-y-4"
>
@csrf
@if(session('success'))
<x-alert type="success">{{ session('success') }}</x-alert>
@endif
<x-form.input
name="name"
label="Name"
:value="old('name')"
required
/>
<x-form.input
name="email"
type="email"
label="Email"
:value="old('email')"
required
/>
<x-form.textarea
name="message"
label="Message"
:value="old('message')"
required
/>
<x-button type="submit">Send Message</x-button>
</form>
@endfragment<?php
// ContactController.php
public function store(Request $request): View
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email',
'message' => 'required|string|max:1000',
]);
// Process contact form...
return view('components.contact-form')
->fragment('contact-form')
->with('success', 'Message sent successfully!');
}Layout with HTMX Setup
{{-- resources/views/components/layouts/app.blade.php --}}
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ $title ?? config('app.name') }}</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
{{-- HTMX --}}
<script src="https://unpkg.com/htmx.org@2.0.0"></script>
{{-- HTMX CSRF Token --}}
<script>
document.body.addEventListener('htmx:configRequest', (event) => {
event.detail.headers['X-CSRF-TOKEN'] = '{{ csrf_token() }}';
});
</script>
</head>
<body hx-boost="true">
<x-navigation />
<main class="container mx-auto px-4 py-8">
{{ $slot }}
</main>
<x-footer />
{{-- Toast notifications for HTMX --}}
<div id="toast-container" class="fixed bottom-4 right-4 space-y-2"></div>
</body>
</html>Layout Component
Main Layout
{{-- resources/views/components/layouts/app.blade.php --}}
@props([
'title' => null,
'description' => null,
])
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="h-full">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ $title ? $title . ' - ' : '' }}{{ config('app.name') }}</title>
@if ($description)
<meta name="description" content="{{ $description }}">
@endif
{{-- Fonts --}}
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=inter:400,500,600,700" rel="stylesheet" />
{{-- Vite Assets --}}
@vite(['resources/css/app.css', 'resources/js/app.js'])
{{-- Additional Head Content --}}
{{ $head ?? '' }}
</head>
<body class="h-full bg-gray-50 font-sans antialiased">
{{-- Skip Link --}}
<a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:p-4 focus:bg-white">
Skip to main content
</a>
{{-- Navigation --}}
@if (isset($navigation))
{{ $navigation }}
@else
<x-layouts.navigation />
@endif
{{-- Page Header --}}
@if (isset($header))
<header class="bg-white shadow">
<div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
{{ $header }}
</div>
</header>
@endif
{{-- Main Content --}}
<main id="main-content" class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
{{-- Flash Messages --}}
@if (session('success'))
<x-alert type="success" :message="session('success')" class="mb-6" />
@endif
@if (session('error'))
<x-alert type="error" :message="session('error')" class="mb-6" />
@endif
{{ $slot }}
</main>
{{-- Footer --}}
@if (isset($footer))
<footer class="bg-white border-t">
<div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
{{ $footer }}
</div>
</footer>
@else
<x-layouts.footer />
@endif
{{-- Scripts --}}
{{ $scripts ?? '' }}
</body>
</html>Navigation Component
{{-- resources/views/components/layouts/navigation.blade.php --}}
<nav class="bg-white shadow" x-data="{ open: false }">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="flex h-16 justify-between">
{{-- Logo --}}
<div class="flex items-center">
<a href="{{ route('home') }}" class="flex items-center">
<x-application-logo class="h-8 w-auto" />
</a>
</div>
{{-- Desktop Navigation --}}
<div class="hidden sm:flex sm:items-center sm:space-x-8">
<x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
Dashboard
</x-nav-link>
<x-nav-link :href="route('projects.index')" :active="request()->routeIs('projects.*')">
Projects
</x-nav-link>
{{-- User Menu --}}
@auth
<x-dropdown>
<x-slot:trigger>
<button class="flex items-center text-sm">
{{ Auth::user()->name }}
<x-heroicon-s-chevron-down class="ml-1 h-4 w-4" />
</button>
</x-slot>
<x-dropdown.item :href="route('profile')">Profile</x-dropdown.item>
<x-dropdown.item :href="route('settings')">Settings</x-dropdown.item>
<x-dropdown.divider />
<form method="POST" action="{{ route('logout') }}">
@csrf
<x-dropdown.item as="button" type="submit">Log Out</x-dropdown.item>
</form>
</x-dropdown>
@else
<x-nav-link :href="route('login')">Log In</x-nav-link>
@endauth
</div>
{{-- Mobile Menu Button --}}
<div class="flex items-center sm:hidden">
<button @click="open = !open" class="p-2">
<x-heroicon-o-bars-3 x-show="!open" class="h-6 w-6" />
<x-heroicon-o-x-mark x-show="open" class="h-6 w-6" x-cloak />
</button>
</div>
</div>
</div>
{{-- Mobile Menu --}}
<div x-show="open" x-cloak class="sm:hidden">
<div class="space-y-1 px-4 pb-3 pt-2">
<x-mobile-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
Dashboard
</x-mobile-nav-link>
</div>
</div>
</nav>Footer Component
{{-- resources/views/components/layouts/footer.blade.php --}}
<footer class="bg-white border-t mt-auto">
<div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
<div class="flex flex-col sm:flex-row justify-between items-center">
<p class="text-sm text-gray-500">
© {{ date('Y') }} {{ config('app.name') }}. All rights reserved.
</p>
<nav class="flex space-x-4 mt-4 sm:mt-0">
<a href="/privacy" class="text-sm text-gray-500 hover:text-gray-700">Privacy</a>
<a href="/terms" class="text-sm text-gray-500 hover:text-gray-700">Terms</a>
</nav>
</div>
</div>
</footer>Page Usage
{{-- resources/views/dashboard.blade.php --}}
<x-layouts.app title="Dashboard" description="Your personal dashboard">
<x-slot:header>
<h1 class="text-2xl font-bold text-gray-900">Dashboard</h1>
</x-slot>
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
<x-card>
<x-slot:header>Statistics</x-slot>
<p>Content here...</p>
</x-card>
</div>
<x-slot:footer>
<p class="text-center text-sm text-gray-500">Custom footer for this page</p>
</x-slot>
</x-layouts.app>Vite Integration
Decision Tree
Using Tailwind CSS?
├── YES → v4? Use @tailwindcss/vite plugin
│ v3? Use PostCSS config
└── NO → Standard laravel-vite-plugin only
│
Using Vue/React?
├── YES → Add framework plugin
└── NO → Just CSS + JS entry pointsSetup Options
| Stack | Plugins Needed |
|---|---|
| Tailwind v4 | laravel-vite-plugin + @tailwindcss/vite |
| Tailwind v3 | laravel-vite-plugin + PostCSS |
| Vue | laravel-vite-plugin + @vitejs/plugin-vue |
| React | laravel-vite-plugin + @vitejs/plugin-react |
| Plain JS/CSS | laravel-vite-plugin only |
Blade Directives
| Directive | Use When |
|---|---|
@vite(['...']) | Include CSS/JS assets |
@viteReactRefresh | React Fast Refresh |
Commands
| Command | Purpose |
|---|---|
npm run dev | Start dev server with HMR |
npm run build | Production build |
Entry Points
| File Type | Default Location |
|---|---|
| CSS | resources/css/app.css |
| JavaScript | resources/js/app.js |
| Images | resources/images/ |
Asset URLs
| Context | Method |
|---|---|
| In Blade | Vite::asset('resources/images/logo.png') |
| In CSS | Relative paths work |
| In JS | import logo from '@/images/logo.png' |
Environment Variables
| Location | Prefix | Access |
|---|---|---|
.env | VITE_ | import.meta.env.VITE_* |
| Laravel | None | Not exposed to frontend |
Common Patterns
| Pattern | Solution |
|---|---|
| Page-specific JS | Multiple entry points |
| Vendor split | Vite handles automatically |
| Legacy browsers | @vitejs/plugin-legacy |
| PWA | vite-plugin-pwa |
SSR (Server-Side Rendering)
| Setup | Use When |
|---|---|
| Inertia SSR | Vue/React with Inertia |
@vite with SSR | Hybrid rendering |
| File | Purpose |
|---|---|
resources/js/ssr.js | SSR entry point |
bootstrap/ssr/ | SSR build output |
Inertia.js Integration
| Stack | Setup |
|---|---|
| Vue + Inertia | @vitejs/plugin-vue + @inertiajs/vue3 |
| React + Inertia | @vitejs/plugin-react + @inertiajs/react |
| Svelte + Inertia | @sveltejs/vite-plugin-svelte |
CSP (Content Security Policy)
| Directive | Use When |
|---|---|
@vite(['...']) | Standard (auto nonce) |
Vite::useCspNonce($nonce) | Custom CSP nonce |
Vite::useScriptTagAttributes([...]) | Custom attributes |
Hot Reload Config
| Setting | Purpose |
|---|---|
server.host | Network access |
server.hmr.host | HMR websocket host |
server.https | HTTPS in dev |
| Environment | Config |
|---|---|
| Docker | Set VITE_HOST in .env |
| Sail | Auto-configured |
| Homestead | Manual host config |
File Organization
| File | Purpose |
|---|---|
vite.config.js | Vite configuration |
resources/css/app.css | Main CSS entry |
resources/js/app.js | Main JS entry |
resources/js/ssr.js | SSR entry (if SSR) |
public/build/ | Production output |
→ Code examples: See LayoutComponent.blade.md