A set of javascript free Tailwind-styled Blade components for making building blade templates easier.
- Components for standard form elements with Tailwind CSS classes out of the box
- Automatic error rendering alongside each input
- Old-input repopulation via
old()after failed form submissions - Method spoofing for
PUT,PATCH, andDELETEforms with automatic CSRF output - Dot-notation conversion for array field names (e.g.
items[0][name]→items.0.name) - ARIA attributes (
aria-required,aria-disabled,aria-readonly) applied automatically - Easy override customisations
- PHP 8.2 or higher
- Laravel 12 or higher
Install the package with composer:
composer require adzchappers/blade-componentsOptionally publish the configuration file:
php artisan vendor:publish --tag="blade-components-config"The package's Blade views ship with Tailwind v4 utility classes. Add a @source directive to your CSS entry point so Tailwind scans the package views, and load the @tailwindcss/forms plugin:
/* app.css */
@import "tailwindcss";
@plugin "@tailwindcss/forms";
@source "../../vendor/adzchappers/blade-components/resources/views";Adjust the @source path to be relative to your CSS file's location within your project.
After publishing, config/blade-components.php exposes two keys:
prefix- A string prepended to every component name. With'prefix' => 'ac',<x-form />becomes<x-ac-form />. Defaults to''(no prefix).components- A map of component name →['class' => ..., 'view' => ...]. Override either entry to swap in a custom class or Blade view for any component.
// config/blade-components.php
return [
'prefix' => '',
'components' => [
'form-input' => [
'class' => \App\View\Components\MyFormInput::class,
'view' => 'blade-components::forms.form-input',
],
// ...
],
];Renders an HTML <form> tag. Automatically outputs a CSRF token and a spoofed method field when using PUT, PATCH, or DELETE. Normalises the method attribute to uppercase.
| Prop | Type | Default | Description |
|---|---|---|---|
method |
string |
'POST' |
HTTP method. Normalised to uppercase. |
has-files |
bool |
false |
Adds enctype="multipart/form-data". |
Additional attributes (e.g. class, action, id) are merged onto the <form> element.
<x-form action="{{ route('users.store') }}">
{{-- CSRF added automatically --}}
<x-form-input name="name" label="Name" required />
<x-form-button>Save</x-form-button>
</x-form>
{{-- PUT form with file upload --}}
<x-form method="PUT" action="{{ route('users.update', $user) }}" has-files>
{{-- CSRF + _method=PUT added automatically --}}
<x-form-input name="avatar" type="file" label="Avatar" />
<x-form-button>Update</x-form-button>
</x-form>Wraps content in a <fieldset> with an optional <legend>. Supports disabling the entire group.
| Prop | Type | Default | Description |
|---|---|---|---|
legend |
string|null |
null |
Legend text. Omitted when not set. |
disabled |
bool |
false |
Adds disabled aria-disabled="true". |
<x-form-fieldset legend="Personal details" class="space-y-4">
<x-form-input name="first_name" label="First name" />
<x-form-input name="last_name" label="Last name" />
</x-form-fieldset>
{{-- Disabled group --}}
<x-form-fieldset legend="Billing" :disabled="true">
<x-form-input name="card_number" label="Card number" />
</x-form-fieldset>All input components share these behaviours: old-input repopulation, automatic error display, and ARIA attribute rendering. See Behaviours for details.
Renders a single <input> element wrapped in a <div> with an optional <label> and error message. Hidden inputs (type="hidden") render without the wrapper, label, or error.
| Prop | Type | Default | Description |
|---|---|---|---|
name |
string |
- | Field name. Required. |
id |
string|null |
null |
Element id. Auto-generated from name when not set. |
label |
string|null |
null |
Label text. No <label> rendered when omitted. |
type |
string |
'text' |
Input type (e.g. email, password, file, hidden). |
placeholder |
string|null |
null |
Placeholder text. |
value |
string|null |
null |
Initial value. Overridden by old input when present. |
required |
bool |
false |
Adds required aria-required="true". |
disabled |
bool |
false |
Adds disabled aria-disabled="true". |
readonly |
bool |
false |
Adds readonly aria-readonly="true". |
show-error |
bool |
true |
Renders the field error message. Set to false to suppress. |
<x-form-input name="email" type="email" label="Email address" placeholder="you@example.com" required />
<x-form-input name="amount" type="number" label="Amount (£)" value="0.00" />
<x-form-input type="hidden" name="_locale" value="en" />Renders a <textarea> with an optional <label> and error message.
| Prop | Type | Default | Description |
|---|---|---|---|
name |
string |
- | Field name. Required. |
id |
string|null |
null |
Element id. Auto-generated from name when not set. |
label |
string|null |
null |
Label text. No <label> rendered when omitted. |
placeholder |
string|null |
null |
Placeholder text. |
value |
string|null |
null |
Initial content. Overridden by old input when present. |
required |
bool |
false |
Adds required aria-required="true". |
disabled |
bool |
false |
Adds disabled aria-disabled="true". |
readonly |
bool |
false |
Adds readonly aria-readonly="true". |
show-error |
bool |
true |
Renders the field error message. Set to false to suppress. |
<x-form-textarea
name="description"
label="Description"
placeholder="Enter a description…"
:value="$model->description"
required
/>Renders a <select> element with an optional <label> and error message.
| Prop | Type | Default | Description |
|---|---|---|---|
name |
string |
- | Field name. Required. |
id |
string|null |
null |
Element id. Auto-generated from name when not set. |
label |
string|null |
null |
Label text. No <label> rendered when omitted. |
options |
array |
[] |
Associative array of value => label pairs. |
selected |
string|array|null |
null |
Selected value(s). Overridden by old input when present. |
multiple |
bool |
false |
Enables multi-select. |
required |
bool |
false |
Adds required aria-required="true". |
disabled |
bool |
false |
Adds disabled aria-disabled="true". |
readonly |
bool |
false |
Adds readonly aria-readonly="true". |
show-error |
bool |
true |
Renders the field error message. Set to false to suppress. |
<x-form-select
name="country"
label="Country"
:options="['gb' => 'United Kingdom', 'us' => 'United States', 'ca' => 'Canada']"
:selected="$user->country"
/>
{{-- Multi-select --}}
<x-form-select
name="roles"
label="Roles"
:options="$roles"
:selected="$user->roles->pluck('id')->all()"
multiple
/>Renders a single <input type="checkbox"> with an optional <label> and error message. Handles old-input correctly - if the form was previously submitted and the checkbox was unchecked, it stays unchecked. Whilst errors display as default on checkboxes, you might want to set them to false if you have a group of checkboxes and call the error manually so the errors aren't duplicated after every checkbox.
| Prop | Type | Default | Description |
|---|---|---|---|
name |
string |
- | Field name. Required. |
id |
string|null |
null |
Element id. Auto-generated from name when not set. |
label |
string|null |
null |
Label text. No <label> rendered when omitted. |
value |
string|null |
'1' |
The value submitted when checked. |
checked |
bool |
false |
Whether the checkbox is initially checked. |
required |
bool |
false |
Adds required aria-required="true". |
disabled |
bool |
false |
Adds disabled aria-disabled="true". |
readonly |
bool |
false |
Adds readonly aria-readonly="true". |
show-error |
bool |
true |
Renders the field error message. Set to false to suppress. |
<x-form-checkbox name="terms" label="I agree to the terms and conditions" required />
<x-form-checkbox name="newsletter" label="Subscribe to newsletter" :checked="$user->newsletter" />Renders a single <input type="radio"> with an optional <label>. Typically used inside an <x-form-fieldset> to group related options. Whilst errors display as default on radios, you might want to set them to false if you have a group of radios and call the error manually so the errors aren't duplicated after every radio.
| Prop | Type | Default | Description |
|---|---|---|---|
name |
string |
- | Field name. Required. |
id |
string|null |
null |
Element id. Auto-generated from name when not set. |
label |
string|null |
null |
Label text. No <label> rendered when omitted. |
value |
string|null |
'1' |
The value submitted when this option is selected. |
checked |
bool |
false |
Whether this option is initially selected. |
required |
bool |
false |
Adds required aria-required="true". |
disabled |
bool |
false |
Adds disabled aria-disabled="true". |
readonly |
bool |
false |
Adds readonly aria-readonly="true". |
show-error |
bool |
true |
Renders the field error message. Set to false to suppress. |
<x-form-fieldset legend="Preferred contact method">
<x-form-radio name="contact" value="email" label="Email" :checked="$user->contact === 'email'" />
<x-form-radio name="contact" value="phone" label="Phone" :checked="$user->contact === 'phone'" />
<x-form-radio name="contact" value="post" label="Post" :checked="$user->contact === 'post'" />
</x-form-fieldset>Renders a <label> element. Used automatically by input components when label is set, but also available standalone for custom layouts.
| Prop | Type | Default | Description |
|---|---|---|---|
for |
string |
- | The id of the associated input. Required. |
required |
bool |
false |
Appends a required indicator (*) to the label text. |
<x-form-label for="email" required>Email address</x-form-label>
<input id="email" type="email" name="email" />Renders a <button> element. Validates the type attribute and falls back to submit for unrecognised values. When the slot is empty, the button renders the translatable string Submit as its label.
| Prop | Type | Default | Description |
|---|---|---|---|
type |
string |
'submit' |
Button type: submit, button, or reset. |
<x-form-button>Save changes</x-form-button>
<x-form-button type="button" class="btn-secondary">Cancel</x-form-button>
<x-form-button type="reset">Clear form</x-form-button>Renders the first validation error message for a named field. Used automatically by input components, but also available standalone.
| Prop | Type | Default | Description |
|---|---|---|---|
name |
string |
- | Field name to look up. Supports array notation (e.g. items[0][name]). Required. |
bag |
string |
'default' |
The error bag to read from. |
<input type="text" name="email" />
<x-form-error name="email" />Renders all validation errors from an error bag as an unordered list. Useful at the top of a form to summarise every failure.
| Prop | Type | Default | Description |
|---|---|---|---|
bag |
string |
'default' |
The error bag to read from. |
<x-form action="{{ route('users.store') }}">
<x-form-error-list />
<x-form-input name="name" label="Name" />
<x-form-input name="email" type="email" label="Email" />
<x-form-button>Save</x-form-button>
</x-form>After a failed form submission, x-form-input, x-form-textarea, x-form-select, x-form-checkbox, and x-form-radio automatically call old() to restore the user's previous input. You do not need to write :value="old('field', $default)" manually.
x-form-checkbox handles the unchecked case correctly - HTML forms do not include unchecked checkboxes in the submission, so the component checks session()->hasOldInput() before deciding whether to restore state.
Input components render an <x-form-error> for their field by default. To suppress it for a specific field, pass :show-error="false":
<x-form-input name="email" :show-error="false" />Use <x-form-error-list /> to render all errors in one place at the top of the form instead.
<x-form> outputs a CSRF token on every state-changing form (anything other than GET, HEAD, or OPTIONS). When method is PUT, PATCH, or DELETE, it also outputs a hidden _method field and sets the <form method="POST">, conforming to Laravel's method spoofing:
<x-form method="DELETE" action="{{ route('users.destroy', $user) }}">
{{-- Renders: <input type="hidden" name="_method" value="DELETE"> --}}
<x-form-button>Delete account</x-form-button>
</x-form>Field names written in array notation (e.g. items[0][name]) are automatically converted to dot notation (items.0.name) when looking up validation errors and old input. This means standard Laravel validation error keys work correctly for array fields:
@foreach ($items as $index => $item)
<x-form-input name="items[{{ $index }}][name]" label="Item name" :value="$item->name" />
@endforeachValidation errors keyed as items.0.name, items.1.name, etc. will appear next to the correct field.
composer test # PHPUnit + PHPStan
composer test:phpunit # PHPUnit only
composer test:phpstan # PHPStan static analysis
composer pint # check/fix code stylePlease see CHANGELOG for more information on what has changed recently.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.