Skip to main content

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 external aria-*.
  • 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

X

Graph View