Skip to main content
WireKit
Copy for LLM

Toggle

A switch control for binary on/off settings. Toggles are visually distinct from checkboxes and communicate an immediate state change (like flipping a lightswitch) rather than a form selection to be submitted later.

Basic Usage

Simple toggle

Sizes

Small, medium, large

With Hint

Toggle with helper text

Occasional product updates, no more than once a month.

States

Checked and disabled

Livewire Integration

The toggle is a native <input type="checkbox">wire:model works out of the box:

<x-wirekit::toggle
    name="notify"
    label="Notify me"
    wire:model.live="notifyEnabled"
/>

Optimistic UI

Pass the name of the Livewire method the toggle should call and it shows the new state immediately, then confirms or undoes it when the server answers:

<x-wirekit::toggle
    name="notify"
    label="Notify me"
    :checked="$notifyEnabled"
    optimistic="toggleNotify"
/>

Try it

The demo below runs the real path: the flip shows immediately, the outline says the value is provisional, and the server's answer either confirms it silently or takes it back.

Optimistic toggle — accept, reject, and a slow answer

The <livewire:demos.…> wrapper above exists only on this site — it supplies the three demo methods so the page can show a real round trip. The block under it is what you write.

It is a method name rather than a boolean because the component cannot know the action otherwise — server actions reach a WireKit component through the attribute bag, so the component never sees your wire:click.

optimistic replaces wire:model.live here rather than joining it. The method you name is what changes the value on the server, and the toggle shows the change while that call is in flight. Pass the current value with :checked so the toggle knows where it started; a plain wire:model (without .live) alongside it is still fine, since it only submits with the surrounding form.

Load wirekit-optimistic.js alongside whichever bundle you already use — below it, in your layout:

@wirekitScripts
<script src="{{ asset('vendor/wirekit/wirekit-optimistic.js') }}"></script>

It is a separate file on purpose: loading it is how you opt into the announcement behavior described below, so applications that do not use it pay nothing for it. Without it the toggle keeps working exactly as it always has — the prop simply has nothing to mount.

What a screen reader hears The flip announces once, hedged — "Saving" — so the state is audible as provisional. Confirmation is silent: what was announced is what happened. Only a deviation speaks a second time, which is what makes the undo recognizable as an undo rather than as another confirmation.

Where the toggle already shows a validation error, the undo stays silent and leaves the field's own message to speak. "Email is required" tells you what to do; "could not save" does not.

An aborted request announces nothing at all — nothing was refused, so there is nothing to take back.

Focus stays exactly where you put it. An undo arrives on the server's schedule, and moving focus then would take you out of your place for a reason you could not predict.

A second flip while one is in flight is refused rather than queued. Queued, the final state would depend on the order the responses came back in — network timing — which is both wrong and impossible to test.

Scope. This covers mutations: changing something and showing it right away. Sorting, filtering, pagination and form submission are query round trips — nobody can show a result nobody knows yet — and are deliberately not covered.

Design Decisions

  • Why role="switch" instead of role="checkbox"? A switch represents an immediate state change (the setting applies right away). A checkbox represents a selection in a form (applied on submit). This distinction matters for screen reader users.
  • Why a native <input> instead of Alpine? Native inputs are zero-JS, fully compatible with Livewire's wire:model, and inherit form submission behavior for free.

Props

Prop Type Default Description
label string|null null Text label displayed next to the toggle
hint string|null null Helper text rendered below
error string|null null Error message (also triggers aria-invalid)
size string 'md' sm, md, lg
scope string|null null Scoped personalization name
optimistic string|null null Livewire method to call, showing the new state before the server confirms it. See Optimistic UI.
optimisticArgs array [] Extra arguments appended to the optimistic action call, after the new value — the row this control belongs to.
announceError bool|null null Render the error message in a live region. Falls back to the parent form, then to config.

All other attributes (name, id, checked, disabled, wire:model, etc.) pass through to the underlying <input>.

Accessibility

  • The underlying <input> has role="switch", which tells assistive technology this is an on/off control (not a standard checkbox).
  • The visual track and knob are purely decorative (aria-hidden="true"). Screen readers announce the native input's checked state.
  • Focus ring appears on the track via peer-focus-visible:* utilities — keyboard users always see which toggle is focused.
  • aria-invalid, aria-describedby are set automatically when error or hint are present.

Keyboard Interaction

Key Action
Tab Move focus to the button
Enter / Space Activate the button

Pitfalls

  • Don't use a toggle for an action that requires a confirmation step. Toggles should commit instantly; pair them with reversible state. For destructive actions use a button + <x-wirekit::alert-dialog>.
  • Don't bind wire:model.live if the action is expensive. Each flick of the toggle round-trips. Debounce, or commit on a save button.

Design Tokens

Token Purpose
--color-wk-accent Track background when checked
--color-wk-bg-muted Track background when off
--color-wk-bg-elevated Knob background
--color-wk-border-strong Track border when off
--color-wk-ring Focus ring color
--shadow-wk-sm Knob shadow
--transition-wk-duration Knob slide + color animation
--opacity-wk-disabled Dimmed state when disabled

Customization

Override defaults in config/wirekit.php:

'components' => [
    'toggle' => ['size' => 'lg'],
],

Was this page helpful?

Voting requires cookies or local storage. What we store