Form
<x-wirekit:: is a real <form> element that also carries one setting
governing how every control inside announces its validation errors to screen
readers. Drop your wire:submit, action, and method onto it exactly as you
would a native form.
Fields Outside the Form
A field that stands outside the form, in a table row or a sidebar, joins it with form and
the form's id, the way a native field does. Every form component hands the attribute to the
field it submits, so its value arrives with the form, a hidden field included.
Resetting the Form
A reset button, or form.reset() from your own script, returns every form component to the
value it was rendered with, as it returns a native field to its value. That includes a
component that keeps its value in a hidden field, which sends and shows its starting value
again, and one that joins the form from outside it through form. A reset that a listener
cancels with prevent leaves every component as it was, as it leaves a native field.
The Focus After a Refused Save
When the server refuses a save, the focus stays on the button the reader pressed, and the fields
that need them can be anywhere on the page, further down or folded away in a card. Dispatch
wirekit:focus-invalid with the refusal, and the focus goes to the first field of the page that
carries aria-invalid="true", which every WireKit field sets on its error.
The component validates, and dispatches the event before it lets the refusal go on:
<?php
namespace App\Livewire;
use Illuminate\Validation\ValidationException;
use Livewire\Attributes\Validate;
use Livewire\Component;
class ShippingSettings extends Component
{
#[Validate('required')]
public string $returnAddress = '';
public function save(): void
{
try {
// 1. Check every rule before anything is stored.
$this->validate();
} catch (ValidationException $exception) {
// 2. Take the reader to the first field the rules refused.
$this->dispatch('wirekit:focus-invalid');
// 3. Let Livewire show the messages as it always does.
throw $exception;
}
// 4. Store the settings here.
}
}
The view puts the field in a card that stays closed until something in it needs the reader:
<x-wirekit::form id="shipping-settings" wire:submit="save">
{{-- 1. A card that folds. A refused field inside opens it. --}}
<x-wirekit::card collapsible heading="Returns">
<x-wirekit::card.body>
<x-wirekit::input name="returnAddress" wire:model="returnAddress" label="Return address" />
</x-wirekit::card.body>
</x-wirekit::card>
{{-- 2. The save the server may refuse. --}}
<x-wirekit::button type="submit">Save</x-wirekit::button>
</x-wirekit::form>
Whatever hides the field opens first: every collapsible card
around it, the step of a wizard that holds it, and a panel the browser's find
in page could open, such as an accordion item or a
collapsible. The focus follows within a few frames, once the field shows.
A field that never shows, inside a closed dialog for example, passes the focus on to the next
rejected field. A rejected group, a list of radios for example, gives the focus to its first entry.
within keeps the search inside one element, named by its id, such as the form above:
// In save(), in place of the dispatch above: search only the shipping settings.
$this->dispatch('wirekit:focus-invalid', within: 'shipping-settings');
The focus moves only when the event asks for it. A field that turns invalid while the reader types
in it, under wire:model.live, leaves the focus where the reader is.
Why a form-level setting
Every WireKit form control can announce its error in an aria-live region so a
validation message that appears dynamically is read out. That is the right
default — but whether you want it is a property of the form, not the app:
- A form that renders its own error summary — a
role="alert"list that announces every message and pulls focus (the standard WCAG 3.3.1 pattern) — should turn the per-field live regions off, or every error is announced twice. - A form with no summary needs them on, or a dynamically-appearing error is never announced at all.
The global wirekit. config can only pick one for the whole
app. <x-wirekit:: scopes the choice to a single form:
{{-- This form renders its own summary, so silence the per-field live regions. --}}
<x-wirekit::form :announce-errors="false" wire:submit="login">
{{-- your own <div role="alert"> summary here --}}
<x-wirekit::input name="email" label="Email" />
<x-wirekit::input name="password" type="password" label="Password" />
</x-wirekit::form>
Precedence
An explicit :announce-error on a single control always wins, so you can opt one
control back in even inside a silenced form:
<x-wirekit::form :announce-errors="false">
<x-wirekit::input name="email" label="Email" />
{{-- this one still announces, summary or not --}}
<x-wirekit::input name="code" label="2FA code" :announce-error="true" />
</x-wirekit::form>
The order, highest priority first:
- A per-control
:announce-error. - The surrounding
<x-wirekit::.form :announce-errors="…"> - The global
wirekit.config (defaulta11y. announce_ error true).
A <x-wirekit:: follows the same order for its group error: its own :announce-error, then the form, then the config.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
announceErrors |
bool|null |
null |
false silences every control's aria-live error region inside this form; true forces it on; null inherits the global config. |
scope |
string|null |
null |
Class-scope override for the <form> element. |
All other attributes (wire:submit, action, method, class, …) pass through
to the underlying <form>.
Accessibility
- When
announce-errorsisfalse, provide your own announcement — arole="alert"error summary is the usual choice, and it should receive focus when submission fails so the reader is taken straight to it. - When
announce-errorsis lefttrue/null, each control announces its own error in a polite live region; do not also render a summary, or the reader hears every message twice.
Keyboard Interaction
The wrapper is a native <form> and adds no keyboard behavior of its own — the
controls inside keep theirs, and Enter in a text field submits the
form exactly as the browser does natively.