Blade components in Livewire: what works, and the limits
Using Bladewell inside Livewire 3 and 4: wire:model on every form control, no wire:ignore, state that survives re-renders, updates to code you've edited, and the few real limits.
php artisan bladewell:add text-input select datepicker captcha button file-upload progress
Uses Text input, Select, Date picker, Captcha, Button, File upload and Progress. New to Bladewell? Install it first.
The short answer
Every Bladewell component works inside Livewire 3 and 4 components, checked in a real browser against Livewire 3.8 and 4.4. Form controls bind with wire:model, components keep what the person did through every render, and you write no events, listeners or wire:ignore to get there. Livewire isn't required either: the same tags work on a plain Blade page, because each one is server-rendered Blade plus a small vanilla JS file.
There are a few real limits, listed at the end.
Does wire:model work?
Yes, directly on the component, with no name needed. That covers the text inputs, select, date and time pickers, tags, phone, file upload, captcha and the rest. Each one shows the property's value after every render, a value you set in PHP included, and shows the property's validation errors under itself.
<form wire:submit="save" class="grid gap-5">
<x-widget.text-input label="Name" wire:model="name" required />
<x-widget.select label="Country" wire:model.live="country" :options="['nl' => 'Netherlands', 'in' => 'India', 'us' => 'United States']" placeholder="Pick a country" />
<x-widget.datepicker label="Date of birth" wire:model="birthday" />
<x-widget.captcha provider="turnstile" label="Security check" wire:model="token" />
<x-widget.button type="submit">Sign up</x-widget.button>
</form>
The component validates as usual. Errors come back under each field, and the captcha's Turnstile token arrives in its property like any other value.
namespace App\Livewire;
use App\Rules\Captcha;
use Livewire\Component;
class SignUp extends Component
{
public string $name = '';
public ?string $country = null;
public ?string $birthday = null;
// The captcha's token, from Turnstile.
public string $token = '';
public function save(): void
{
$this->validate([
'name' => ['required', 'string', 'max:120'],
'country' => ['required', 'in:nl,in,us'],
'birthday' => ['required', 'date', 'before:today'],
'token' => ['required', new Captcha('turnstile')],
]);
// …
}
public function render()
{
return view('livewire.sign-up');
}
}
Do I need wire:ignore?
No. The components already shield the parts their scripts build, so Livewire's morph leaves them alone. Don't wrap a form control in wire:ignore yourself: it then stops showing values set in PHP and validation errors.
The one exception is a progress bar you drive from JavaScript with progress.set(). Without wire:ignore, the next render puts back the server's value. Third-party scripts you add yourself, such as a chart or a rich-text editor, need one too, as they would in any Livewire app.
{{-- A bar your own script drives with progress.set() is the one place a widget needs wire:ignore. --}}
<div x-on:livewire-upload-progress="progress.set('upload', $event.detail.progress)" class="grid gap-3">
<x-widget.file-upload label="Video" wire:model="video" />
<div wire:ignore>
<x-widget.progress id="upload" label="Uploading" show-label />
</div>
</div>
Does state survive a re-render?
Yes. What the person did stays as they left it while the content around it updates:
- An open modal stays open, including on the render that shows validation errors, and an open dropdown menu stays open and in place.
- Accordion panels stay open or shut, and a running stopwatch or timer keeps running.
- The table's sorting, multi-column sorting included, its pagination and its rows per page follow component properties and update the component in place.
- A stepper follows
:current="$step", so a multi-step wizard is one Livewire component. - A kanban card being dragged stays in hand through a render, and each move reaches the component through
wire:kanban-move. - Components added later, by a render or by
wire:navigate, set themselves up.
How does the component talk to the widgets?
Through properties, and through Livewire's own $this->dispatch() for the few things that aren't a value: a toast, and opening or closing a modal.
// In any action: a toast (with <x-widget.toast /> once in the layout), and a modal by its id.
$this->dispatch('toast', type: 'success', message: 'Saved.');
$this->dispatch('modal-open', id: 'edit-profile');
$this->dispatch('modal-close', id: 'edit-profile');
Each component's page shows it inside a Livewire component, under Usage.
How do updates work if I've edited the code?
The components are copied into your app, so a new release changes nothing until you ask. bladewell:add --installed puts the new version into every file you haven't edited. It skips the ones you have edited and lists them, so your changes are never overwritten unless you pass --force. bladewell.lock records which is which; commit it.
php artisan bladewell:add --installed # every file you haven't edited
php artisan bladewell:diff # what the edited ones would miss
php artisan bladewell:add select --force # or take the package version of one component
The package does only the copying, so it stays a dev dependency, and your app runs without it.
The limits
- Edited files are merged by hand.
bladewell:diffshows what an edited file is missing, but it doesn't merge it for you. - Some values come from the server. The pickers, select, number, tags, phone and slider don't redraw for a value set from Alpine or from your own script. Set those from PHP, or bind them with
wire:model. - Some props only set the start. Where the browser owns the state, a prop sets how it starts: an accordion's
open, and a stopwatch or timer, which reads its props once. - Send captchas with wire:submit. A Turnstile, reCAPTCHA or hCaptcha token is good for one check. After a
wire:submitthe captcha gets a new one. Awire:clickbutton still sends the token, but doesn't get a new one. - Blade and Tailwind CSS v4 only. Not a fit for Tailwind v3 or Bootstrap, or for Inertia pages written in React or Vue.