Toggle / Toggle Group
A two-state button that can be pressed or unpressed. ToggleGroup wraps multiple toggles to manage single or multiple selection.
Preview
Alignment (single)
Formatting (multiple)
Variants
Default
Outline
Sizes
Implementation
Toggle.tsx
tsx
import { Toggle, ToggleGroup } from "@/components/ui/Toggle";
import { Bold, Italic, Underline } from "lucide-react";
import { useState } from "react";
// Single toggle (uncontrolled)
<Toggle defaultPressed aria-label="Bold">
<Bold className="w-4 h-4" />
</Toggle>
// Controlled
const [pressed, setPressed] = useState(false);
<Toggle pressed={pressed} onPressedChange={setPressed} aria-label="Italic">
<Italic className="w-4 h-4" />
</Toggle>
// ToggleGroup — single selection
const [align, setAlign] = useState("left");
<ToggleGroup type="single" value={align} onValueChange={setAlign}>
<Toggle aria-label="left">Left</Toggle>
<Toggle aria-label="center">Center</Toggle>
<Toggle aria-label="right">Right</Toggle>
</ToggleGroup>
// ToggleGroup — multiple selection
const [formats, setFormats] = useState([]);
<ToggleGroup type="multiple" value={formats} onValueChange={setFormats}>
<Toggle aria-label="bold"><Bold className="w-4 h-4" /></Toggle>
<Toggle aria-label="italic"><Italic className="w-4 h-4" /></Toggle>
<Toggle aria-label="underline"><Underline className="w-4 h-4" /></Toggle>
</ToggleGroup>Props — Toggle
All native <button> attributes are forwarded.
| Prop | Type | Default | Description |
|---|---|---|---|
pressed | boolean | — | Controlled pressed state. |
defaultPressed | boolean | false | Initial pressed state for uncontrolled usage. |
onPressedChange | (pressed: boolean) => void | — | Called when the pressed state changes. |
size | "sm" | "md" | "lg" | "md" | Controls height and padding. |
variant | "default" | "outline" | "default" | Visual style — default has no border; outline adds a border that highlights when pressed. |
disabled | boolean | false | Prevents interaction and dims the control. |
Props — ToggleGroup
| Prop | Type | Default | Description |
|---|---|---|---|
type* | "single" | "multiple" | — | Single allows one active item at a time; multiple allows any number. |
value | string | string[] | — | Controlled selected value(s). String for single, string[] for multiple. |
onValueChange | (value: string | string[]) => void | — | Called with the new value(s) when selection changes. |
size | "sm" | "md" | "lg" | "md" | Passed to all child Toggle items. |
variant | "default" | "outline" | "outline" | Passed to all child Toggle items. |
ARIA roles
| Element | Role | Key attributes |
|---|---|---|
| Toggle | switch | aria-checked (true/false), aria-label, aria-pressed (alternative) |
| ToggleGroup | group | aria-label |
Keyboard
| Key | Action |
|---|---|
| Space / Enter | Toggle the pressed state of a focused Toggle |
| Tab | Move focus to the next focusable element |
| Shift+Tab | Move focus to the previous focusable element |
Accessibility
- →Each Toggle uses role="switch" with aria-checked — screen readers announce "on" or "off" state.
- →ToggleGroup uses role="group" — child items share their context through aria-label on the group.
- →Always provide aria-label on icon-only toggles — the icon alone is not accessible to screen readers.
- →Focus ring appears on focus-visible — keyboard users navigate with Tab; space/enter activates.
- →For toolbar-style groups (text formatting), consider role="toolbar" and arrow key navigation over Tab.