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 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.
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.
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">witharia-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.