Toggle Group
A toggle group is a row of toggles that belong together. Pick one, like an alignment, or several, like text styles.
import { AlignCenter, AlignLeft, AlignRight, Bold, Italic, Underline } from "lucide-react";
import { Toggle } from "@/components/ui/toggle";
import { ToggleGroup } from "@/components/ui/toggle-group";
export default function ToggleGroupDemo() {
return (
<>
<ToggleGroup aria-label="Text alignment" defaultValue={["left"]}>
<Toggle size="icon" value="left" aria-label="Align left">
<AlignLeft />
</Toggle>
<Toggle size="icon" value="center" aria-label="Align center">
<AlignCenter />
</Toggle>
<Toggle size="icon" value="right" aria-label="Align right">
<AlignRight />
</Toggle>
</ToggleGroup>
<ToggleGroup multiple aria-label="Text style" defaultValue={["bold", "italic"]}>
<Toggle size="icon" value="bold" aria-label="Bold">
<Bold />
</Toggle>
<Toggle size="icon" value="italic" aria-label="Italic">
<Italic />
</Toggle>
<Toggle size="icon" value="underline" aria-label="Underline">
<Underline />
</Toggle>
</ToggleGroup>
<ToggleGroup aria-label="Billing period" defaultValue={["monthly"]}>
<Toggle value="monthly">Monthly</Toggle>
<Toggle value="yearly">Yearly</Toggle>
</ToggleGroup>
<ToggleGroup disabled aria-label="Disabled" defaultValue={["one"]}>
<Toggle value="one">One</Toggle>
<Toggle value="two">Two</Toggle>
</ToggleGroup>
</>
);
}The code
The component is a single file, read from the repository when the site is built. Press Show code to see all of it.
import type { ComponentProps } from "react";
import { ToggleGroup as BaseToggleGroup } from "@base-ui/react/toggle-group";
import { cx } from "@/lib/cx";
// A row of Toggles that belong together. The toggles are attached: only the outer corners are
// round and neighbours overlap by 1px so borders don't double. The pressed (or hovered, or
// focused) one is lifted above its neighbours so its whole border shows.
// Give each Toggle a `value`. By default pressing one releases the others; add `multiple` to
// allow several. Give the group an aria-label.
const attached = [
"[&>button]:relative [&>button]:rounded-none",
"[&>button:first-child]:rounded-l-md [&>button:last-child]:rounded-r-md",
"[&>button:not(:first-child)]:-ml-px",
"[&>button:hover]:z-10 [&>button:focus-visible]:z-10 [&>button[data-pressed]]:z-10",
].join(" ");
export function ToggleGroup({
className,
...props
}: Omit<ComponentProps<typeof BaseToggleGroup>, "className"> & { className?: string }) {
return <BaseToggleGroup className={cx("inline-flex", attached, className)} {...props} />;
}Props
| Prop | Type | Default | What it does |
|---|---|---|---|
multiple | boolean | false | Lets more than one toggle be pressed at a time. |
defaultValue | string[] | — | The values that start pressed, when you let it manage its own state. |
value | string[] | — | The pressed values, when you control them yourself. |
onValueChange | function | — | Called with the new list of pressed values. |
disabled | boolean | false | Turns off every toggle in the group. |
value | string | — | On each Toggle inside. The value that toggle stands for. Required. |
Give the group an aria-label. Every other prop goes to the Base UI ToggleGroup underneath.
The thinking
Some choices are a set: left, center or right, bold and italic. A group makes the set visible as one control.
- Built on Base UI: The group tracks which values are pressed. Each item is your Toggle with a value, so it looks and behaves the same as a lone Toggle.
- One or several: By default pressing a toggle releases the others, like alignment. Add multiple and any number can be pressed, like bold, italic and underline.
- Attached: The toggles share their borders, so the row reads as one segmented control, like a monthly and yearly switch on a pricing table.
Interface design
- Segments: Only the outer corners are rounded, with the md radius, 6px. Neighbours overlap by 1px, so a border is never doubled.
- Pressed: The pressed segment has its accent border drawn above its neighbours, so no part of it is covered.
- Disabled: Every segment is a flat surface with a quiet label.
Interaction
- Press: Click a segment, or press Enter or Space when it has focus. In a single-choice group, pressing one releases the one that was pressed.
- Keyboard: The group is one stop in the tab order, and the arrow keys move between its segments.
- Each segment: Hover, focus and the press animation are the same as a lone Toggle.
Empathy
- Name the group: The group is announced as a group. Give it an aria-label such as Text alignment, as the demo does.
- Name each icon: Icon-only segments need an
aria-labeleach, like Align left. - Pressed is announced: Each segment reports whether it is pressed, so the state isn't carried by color alone.