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.

PropTypeDefaultDescription
pressedbooleanControlled pressed state.
defaultPressedbooleanfalseInitial pressed state for uncontrolled usage.
onPressedChange(pressed: boolean) => voidCalled 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.
disabledbooleanfalsePrevents interaction and dims the control.

Props — ToggleGroup

PropTypeDefaultDescription
type*"single" | "multiple"Single allows one active item at a time; multiple allows any number.
valuestring | string[]Controlled selected value(s). String for single, string[] for multiple.
onValueChange(value: string | string[]) => voidCalled 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

ElementRoleKey attributes
Toggleswitcharia-checked (true/false), aria-label, aria-pressed (alternative)
ToggleGroupgrouparia-label

Keyboard

KeyAction
Space / EnterToggle the pressed state of a focused Toggle
TabMove focus to the next focusable element
Shift+TabMove 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.