Livewire
Every interactive Brok component is built on Alpine.js, not Livewire. Brok offers bounded, first-class Livewire 3 support rather than a Livewire variant of every component:
- A
wire:modelpassthrough guarantee on the form primitives — bind it and it forwards onto the real input. - A small reference set of native Livewire
components (modal, tabs, toast, field, confirm, search) that bridge Alpine state to
$wireat runtime. - Documented interop patterns
(
$wire.entangle,wire:ignore) for wiring any other Alpine-state component to Livewire yourself.
Everything ships using the modern Livewire 3 runtime idiom —
$wire.entangle(…) inside Alpine
x-data, not the older
@entangle Blade directive — so the
same copied markup renders fine with or without Livewire and only activates inside a real Livewire
component.
The wire:model passthrough guarantee
The form primitives forward wire:model (and
.live / .blur) onto the actual
underlying form control — the real <input>,
<select> or <textarea>, never a
presentational wrapper — so Livewire tracks the field, validation and error bags exactly like a native
control. This is verified by an automated test that renders the real component source and asserts the
attribute lands on the control. Guaranteed for:
inputtextareaselectcheckboxradioswitch
The animated checkbox and the switch route
consumer class to their visible control while keeping
wire:* on the hidden native input, so binding stays correct even though the
visible element differs.
{{-- All of these forward onto the real form control --}}
Note
ui:add copies real Blade into your project, the components are
yours — wiring them to Livewire means editing the copied file, exactly as you would any other
Blade component you own.The Livewire reference components
A bounded set of components that bridge Alpine state to $wire at runtime —
the cases where a server bridge genuinely adds value. They guard every $wire
access on window.Livewire, so they render as inert, valid markup (no console
errors) with no Livewire installed, and activate only inside a real Livewire component. Each requires
Livewire 3 in your app.
| Component | Bridges | What it does |
|---|---|---|
livewire-modal |
$wire.entangle |
Open state entangled to a boolean property — the server can open/close it. |
livewire-tabs |
$wire.entangle |
Active tab synced to a Livewire property in both directions. |
livewire-toast |
$wire.on |
Listens for a server-dispatched event and shows a transient toast. |
livewire-field |
$errors |
Wraps a control and renders its Livewire validation message. |
livewire-confirm |
$wire.call |
Confirms, then invokes a Livewire action with optional params. |
livewire-search |
$wire.entangle().live |
Debounced query pushed to a Livewire property for server-side search. |
livewire-modal
Pass wire-open the name of a boolean Livewire property. A UI trigger opens
it; a server action setting the property opens or closes it too.
Delete project
This action cannot be undone.
Cancel
Yes, delete
{{-- public bool $confirmOpen = false; --}}
livewire-tabs
The active tab value entangles wire-model; the Alpine
active binding lets you show panels with x-show
(or render server-side off the property).
…
…
{{-- public string $activeTab = 'overview'; --}}
livewire-toast
Drop one listener near your layout root. Dispatch the event from any Livewire action to raise a toast.
// in a Livewire component
$this->dispatch('notify', message: 'Saved.', variant: 'success');
livewire-field
Wrap any control. livewire-field reads the matching message from Livewire's
shared $errors bag after a wire:model
round-trip and renders it (with the label, hint and ARIA wiring).
livewire-confirm
A destructive/primary button that opens an inline confirmation and calls a Livewire action on confirm, with a built-in busy state.
livewire-search
A debounced search input that entangles wire-model with the
.live modifier, so typing drives a server-side query after the debounce.
{{-- public string $query = '';
public function updatedQuery() { /* filter results */ } --}}
Wiring up other components — the three tools
wire:modelon real inputs. Components that render a native form control (a hidden<input>/<select>, e.g.field, nativeselect,checkbox,radio) can bind directly: putwire:modelon that input and Livewire tracks it like any other field.@entanglefor Alpine state. Components whose state lives in Alpine (switch,dialog,tabs,combobox, …) bridge that state to a Livewire property with@entangle, giving you two-way reactivity in both directions.wire:ignorefor Alpine-owned DOM. When a component renders DOM imperatively (canvas effects, portals, third-party widgets, anything that mutates its own children), wrap it inwire:ignoreso a Livewire re-render doesn't wipe Alpine's work. Usewire:ignore.selfwhen only the element itself (not its children) must survive re-render.
Example 1 — a switch bound to a Livewire property
x-ui.switch keeps its on/off state in Alpine. Bridge that state to a
Livewire $notifications property with @entangle.
Toggling the switch updates the property; changing the property server-side flips the switch.
{{-- resources/views/livewire/notification-settings.blade.php --}}
{{-- The switch's Alpine state is entangled with $notifications.
.live pushes every toggle to the server immediately;
drop it for deferred (next-request) syncing. --}}
Notifications are .
// app/Livewire/NotificationSettings.php
class NotificationSettings extends Component
{
public bool $notifications = false;
public function render()
{
return view('livewire.notification-settings');
}
}
Note
switch Blade already declares its own
x-data. If merging an outer x-data is awkward,
the cleaner pattern is to edit the copied component to accept a model name and entangle internally — see
"Editing the component" below — or expose the entangled value through the component's own state object.Example 2 — a select with wire:model
The native x-ui.select renders a real
<select>, so it needs no Alpine bridge at all — bind
wire:model straight onto it like any Livewire form control.
Selected plan: {{ $plan }}
The same applies to field, checkbox,
radio, textarea and any component that
forwards attributes onto a real input — pass wire:model through and
Livewire validation / error bags work normally.
Example 3 — a dialog opened from server state, kept safe with wire:ignore
A dialog's open/closed state is Alpine-owned. Entangle it so the server can
open it (e.g. after a save), and wrap the dialog in wire:ignore.self so a
Livewire re-render doesn't reset Alpine's transition/focus state mid-animation.
Delete account
This cannot be undone.
{{-- A Livewire action can set $confirmOpen = false to close it. --}}
Confirm
Editing the component (the clean approach)
Because you own the copied Blade, the most robust interop is to add an optional
wire:model hook inside the component. For an Alpine-state
component, read the model name from attributes and entangle when present:
{{-- inside resources/views/components/ui/switch.blade.php --}}
@php($wireModel = $attributes->wire('model')->value())
With that hook in place, consumers write the natural Livewire syntax —
<x-ui.switch wire:model.live="notifications" /> — and the entangle
wiring is hidden inside the component you own. Keep the Alpine-only default path so the component still
works in non-Livewire pages.
Warning
Not first-class support. These are interop recipes, not a shipped
Livewire integration. No Brok component is tested against Livewire's lifecycle, and the
@entangle / wire:ignore wiring is your
responsibility once you adopt it. If your app is Livewire-first and you want zero-glue reactive
components, a runtime Livewire-native package may be the lower-friction choice — see
Why Brok.
Checklist
- Real input rendered? →
wire:modeldirectly on it. - Alpine-held state (open/value/selected)? → bridge with
@entangle. - Component mutates its own DOM (canvas, portal, 3rd-party widget)? → wrap in
wire:ignore/wire:ignore.self. - Reusing across many places? → bake the entangle hook into the copied component.