Skip to main content
Copy for LLM

Form

<x-wirekit::form> 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.

A form with stacked fields

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.

A rating that joins a form from outside it

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 preventDefault() leaves every component as it was, as it leaves a native field.

Change the score or the view, then undo both

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.a11y.announce_error config can only pick one for the whole app. <x-wirekit::form> 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:

  1. A per-control :announce-error.
  2. The surrounding <x-wirekit::form :announce-errors="…">.
  3. The global wirekit.a11y.announce_error config (default true).

A <x-wirekit::field.set> 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-errors is false, provide your own announcement — a role="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-errors is left true/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.

Updated in WireKit v2.67.0 (2026-10-15)

Was this page helpful?

Thank you for your feedback!

Voting requires cookies or local storage. What we store