Reader Stacks

Creating Reusable Blade Components in Laravel

php artisan make:component, passing data as props, and the difference between a class-backed component and an anonymous view-only one.

Creating Reusable Blade Components in Laravel

Blade components let you extract a reusable piece of markup — a button, a card, an alert box — into its own file, called like an HTML tag, with data passed in as attributes.

Creating a component

php artisan make:component Alert

This generates two files: app/View/Components/Alert.php (the class, if class-backed) and resources/views/components/alert.blade.php (the markup).

Passing data in

// app/View/Components/Alert.php
class Alert extends Component
{
    public function __construct(public string $type = 'info') {}

    public function render(): View
    {
        return view('components.alert');
    }
}
{{-- resources/views/components/alert.blade.php --}}
<div class="alert alert-{{ $type }}">
    {{ $slot }}
</div>

Using it

<x-alert type="success">
    Your changes have been saved.
</x-alert>

$slot captures whatever content is placed between the opening and closing tags — the same pattern as a native HTML element's children.

Anonymous components — no class needed

For simpler components that don't need constructor logic, a Blade file alone (no PHP class) works, using @props to declare accepted attributes with defaults:

{{-- resources/views/components/badge.blade.php --}}
@props(['color' => 'gray'])

<span class="badge badge-{{ $color }}">{{ $slot }}</span>

Anonymous components are the better default for markup-only reuse — reach for a class-backed component specifically when you need actual PHP logic (computed values, method calls) behind the component, not just prop pass-through.

Named slots for multi-part components

<x-card>
    <x-slot:header>Order #{{ $order->id }}</x-slot:header>
    Order details go here.
</x-card>

Named slots let a component accept multiple distinct content areas (a header and a body, for example), not just one undifferentiated block of slot content.

Forward ordinary HTML attributes with the attribute bag

A reusable component becomes much more useful when callers can add classes, IDs, ARIA attributes, and data attributes without the component declaring every possible prop. Blade exposes those through $attributes:

@props(['type' => 'info'])

<div {{ $attributes->class(['alert', 'alert-'.$type]) }}>
    {{ $slot }}
</div>
<x-alert type="success" class="mb-4" role="status">
    Saved.
</x-alert>

The component owns its required base classes while the caller can still extend the markup. That is usually cleaner than adding a $class prop and manually concatenating strings.

Props and attributes are not the same thing

Values declared by @props are pulled out for component logic; remaining attributes stay in the attribute bag. That separation lets type="success" influence the template without accidentally rendering a nonstandard type attribute on a div, while aria-live or data-controller can pass through untouched.

Keep database queries out of presentational components

A class-backed component technically can query the database, but that can hide expensive work inside a piece of markup rendered many times in a loop. Pass already-loaded data into the component instead. If a component needs substantial application state, treat it as a deliberate view-model boundary and make the cost obvious rather than turning a badge or card into an invisible query source.

Components are most valuable when they enforce a repeated contract

Extracting three lines of markup used once does not buy much. A component pays off when several views need the same accessibility attributes, states, slots, class conventions, or error behavior; changing that contract in one file is the real benefit, not simply reducing the number of HTML lines in a Blade template.

Topics: Developer Productivity