Blade components
Philosophy
Blade components encapsulate presentation and (optionally) light logic. Following @frontend-guidelines.mdc we ensure:
- Semantic, accessible HTML by default.
- Props for simple data; PHP classes only when you need to pre-process or format.
- kebab-case naming (
forms.input). - Reuse across Storefront, Dashboard, and emails.
Quick creation
php artisan make:component forms.input --view
Generates:
app/View/Components/Forms/Input.php # optional
resources/views/components/forms/input.blade.php
If your component simply displays data, delete the PHP class and use @props:
@props([
'label',
'name',
'type' => 'text',
])
<label for="{{ $name }}" class="tw-block tw-text-sm tw-font-medium">
{{ $label }}
</label>
<input
id="{{ $name }}"
name="{{ $name }}"
type="{{ $type }}"
{{ $attributes->merge(['class' => 'tw-mt-1 tw-w-full tw-rounded']) }}
>
Usage:
<x-forms.input name="email" label="Email" type="email" required />
Logic in the PHP class
When you need data formatting, keep it in the class:
class Price extends Component
{
public function __construct(public float $amount, public string $currency = 'USD') {}
public function formatted(): string
{
return number_format($this->amount, 2).' '.strtoupper($this->currency);
}
public function render()
{
return view('components.price');
}
}
And the view only renders:
<span {{ $attributes->merge(['class' => 'tw-font-semibold']) }}>
{{ $formatted }}
</span>
Accessibility conventions
- Propagate
{{ $attributes }}and merge them to allow externalaria-*. - Use
{{ $slot }}for flexible content. - Handle
required,aria-invalid, and error messages inside the component.
Basic unit test
it('renders input with label', function () {
$html = (string) Blade::render('<x-forms.input name="email" label="Email" />');
expect($html)->toContain('for="email"');
});
Last updated: 2025-07-14