Toggle
A two-state button for toolbar-style on/off actions — a native <button> carrying aria-pressed, which per WAI-ARIA is already a complete toggle button.
Usage#
import { Toggle } from "neelam-ui";
<Toggle size="icon" aria-label="Bold" defaultPressed>
<Bold className="h-4 w-4" aria-hidden="true" />
</Toggle>Toggle, switch, or checkbox?#
Toggle is an action that stays on — bold in an editor, a view filter in a
toolbar. It is not a form value:
- Toggle — a button that stays pressed. Not submitted with a form.
Switch— a setting that applies immediately.Checkbox— a value collected now, submitted later.
If the control belongs in a <form>'s data, it is one of the other two.
Icon-only toggles need a name
size="icon" renders no text, so there is no accessible name unless you pass
aria-label. Without it a screen reader announces only "button, not pressed".
Variants and sizes#
variant is default (transparent until pressed) or outline (a visible
border at rest, for a toolbar where the buttons need to read as a set). Sizes
are sm, md, lg, and icon.
<Toggle variant="outline" size="sm">Grid</Toggle>Controlled#
Unlike Checkbox and Switch, whose state lives on the native input, a plain
<button> has no built-in notion of a persistent pressed state — so Toggle
tracks defaultPressed in its own state. Pass pressed and onPressedChange
to drive it yourself:
const [pressed, setPressed] = useState(false);
<Toggle pressed={pressed} onPressedChange={setPressed} aria-label="Bold">
<Bold className="h-4 w-4" aria-hidden="true" />
</Toggle>Calling preventDefault() in your own onClick cancels the toggle, which is
the escape hatch for "confirm before turning this off".
Keyboard#
| Key | Behaviour |
|---|---|
| Tab | Moves focus to the toggle. Disabled toggles are skipped. |
| EnterSpace | Toggles the pressed state — native button activation. |
Arrow keys are not handled here
A row of toggles is a set of independent buttons, each its own tab stop. A single-tab-stop toolbar with arrow-key roving is a different pattern, and this component deliberately does not impose it.
Accessibility#
aria-presseddoes double duty: it is the state assistive tech reports and the hook the pressed styling is written against, so the two cannot disagree.- A native
<button>, so role, focusability, andEnter/Spaceactivation come from the platform. Nothing is recategorized. typedefaults to"button", so a toggle inside a<form>never submits it by accident.- Screen readers announce "pressed" / "not pressed", which is the distinction between a toggle button and a plain one.
API reference#
| Prop | Type | Default |
|---|---|---|
defaultPressed | boolean | false |
onPressedChange | ((pressed: boolean) => void) | — |
pressed | boolean | — |
size | "sm" | "md" | "lg" | "icon" | null | — |
variant | "default" | "outline" | null | — |
Also accepts every native <button> attribute apart from onChange — use
onPressedChange.