Skip to main content
WireKit
Copy for LLM

Toggle Button

A button that stays pressed: bold, italic, mute, pin. One control, two states, aria-pressed doing the talking.

WireKit has three two-state controls and they are not interchangeable:

Use Component
A form setting the user saves ("Email notifications: on") Toggle — a role="switch" with a label
Pick one of several options Segmented Control
A single control that stays pressed Toggle Button

Basic Usage

Click to toggle

Click either button: it flips between pressed and unpressed. The pressed state is carried by the surface — filled when pressed, outline when not — so it survives a monochrome or high-contrast rendering. It never depends on a tint alone.

These demos use self-toggle, which lets the button manage its own state so it works with no wiring. In a real app you usually want the controlled form below instead, where the pressed state lives in your component.

In a toolbar

Toggle buttons are what a formatting bar is made of.

A formatting cluster

Livewire

The component is controlled: the pressed state lives in your component, not in the button. That is deliberate — the truth about "is this text bold" belongs to the document.

{{-- 1. Bind the state, handle the click — no local JS state to drift --}}
<x-wirekit::toggle-button :pressed="$bold" wire:click="$toggle('bold')" aria-label="Bold">
    B
</x-wirekit::toggle-button>
// 2. One property is the whole state
public bool $bold = false;

Optimistic UI

Pass the name of the Livewire method the button should call and the pressed state flips straight away, while it goes:

<x-wirekit::toggle-button :pressed="$bold" optimistic="toggleBold">Bold</x-wirekit::toggle-button>

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>

Try it

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

Optimistic toggle button — accepted, refused, and a slow answer

Accepted

The press is kept and nothing is said.

Refused

The press is taken back, and said out loud.

Slow to answer

The dashed outline is the provisional state.

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

A refusal puts the state back, because a pressed state is a discrete choice: returning to it costs you nothing, it is simply the other option.

One thing to know before you opt in. The announcement needs an element of its own, and this component is the button — so with optimistic set, a wrapper is added around it. It uses display: contents, so the layout is unchanged and the button keeps its position in any flex or grid row. What does change is the element tree: the button is no longer a direct child of its container, so a selector like .toolbar > button (or :first-child, +, ~) will not match it any more. Without the prop, nothing is added and the markup is exactly as before.

optimistic and selfToggle do not combine — the optimistic layer performs the flip, so a local one would fight it. Setting both lets the layer win.

Props

Prop Type Default Description
pressed bool false The two-state truth; drives aria-pressed and the surface. Also the initial state in self-toggle mode
selfToggle bool false Let the button flip its own state on click, with no wiring (attribute: self-toggle). Handy for a standalone toolbar; leave it off for the controlled Livewire form above
size string 'md' Forwarded to the underlying button
scope string|null null Scoped personalization name
optimistic string|null null Livewire method to call on click, showing the new pressed state before the server agrees. A refusal puts the old state back. Adds a wrapper element — see Optimistic UI.
optimisticArgs array [] Extra arguments appended to the optimistic action call, after the new value — the row this control belongs to.

Everything else — wire:click, aria-label, disabled, classes — passes through to the underlying Button.

Accessibility

  • The control is a real <button type="button"> with aria-pressed — the WAI-ARIA toggle-button pattern. Assistive technology announces "pressed" / "not pressed" without any extra wiring.
  • Never color-only: pressed switches the surface (filled vs outline), so the state is visible in monochrome and high contrast too (WCAG 1.4.1).
  • An icon-only toggle needs its own aria-label — a glyph is not a name.
  • It composes Button, so the focus ring, disabled model and sizes are the same ones used everywhere else.

Keyboard Interaction

Key Action
Tab Focus the button
Enter / Space Toggle the pressed state

Pitfalls

  • Do not use it for a saved setting. That is Toggle, which is a labeled form switch.
  • Do not use it for one-of-many. That is Segmented Control.
  • Do not leave an icon-only toggle unnamed.

Design Tokens

The pressed and unpressed surfaces come from Button's filled and outline surfaces on the neutral intent — so a theme change restyles it with everything else.

Further Reading

Was this page helpful?

Voting requires cookies or local storage. What we store