Skip to Content
ComponentsToggleGroup

Toggle Group

A group of toggle buttons where one or multiple items can be pressed at a time. Built on Base UI’s Toggle and ToggleGroup primitives. In single-select mode, a sliding indicator animates between the active item using Motion (with CSS transition fallback). When the group is hidden (for example with display: none) and shown again, the indicator appears over the pressed item instead of growing from nothing. Only pressing another item slides the indicator: when an item or the group changes size (for example a font size, padding or container width change), it moves to the pressed item at once, so it does not trail behind it.

Import

import { ToggleGroup } from "chunks-ui";

Basic Usage

By default, the toggle group operates in single-select mode. Only one item can be pressed at a time, and a sliding indicator highlights the active selection:

import { AlignLeftIcon, AlignCenterIcon, AlignRightIcon } from 'lucide-react' <ToggleGroup.Root aria-label="Text alignment" defaultValue={["left"]}> <ToggleGroup.Item value="left" aria-label="Align left"><AlignLeftIcon /></ToggleGroup.Item> <ToggleGroup.Item value="center" aria-label="Align center"><AlignCenterIcon /></ToggleGroup.Item> <ToggleGroup.Item value="right" aria-label="Align right"><AlignRightIcon /></ToggleGroup.Item> </ToggleGroup.Root>

Preventing Deselection

Base UI allows deselecting the active item by clicking it again. To enforce that one item is always selected, reject empty values:

<ToggleGroup.Root aria-label="Text alignment" value={value} onValueChange={(newValue) => { if (newValue.length > 0) setValue(newValue); }} > {/* items */} </ToggleGroup.Root>

Multiple Selection

Set multiple to allow pressing more than one item at a time. Each pressed item gets its own background highlight instead of a shared sliding indicator:

import { BoldIcon, ItalicIcon, UnderlineIcon } from 'lucide-react' <ToggleGroup.Root aria-label="Text formatting" multiple defaultValue={["bold", "italic"]}> <ToggleGroup.Item value="bold" aria-label="Bold"><BoldIcon /></ToggleGroup.Item> <ToggleGroup.Item value="italic" aria-label="Italic"><ItalicIcon /></ToggleGroup.Item> <ToggleGroup.Item value="underline" aria-label="Underline"><UnderlineIcon /></ToggleGroup.Item> </ToggleGroup.Root>

Controlled

ToggleGroup.Root infers the type of an item’s value from value or defaultValue, so a typed useState setter is an onValueChange:

import { AlignLeftIcon, AlignCenterIcon, AlignRightIcon } from 'lucide-react' function ControlledToggleGroup() { const [value, setValue] = useState<("left" | "center" | "right")[]>(["left"]); return ( <ToggleGroup.Root aria-label="Text alignment" value={value} onValueChange={setValue}> <ToggleGroup.Item value="left" aria-label="Align left"><AlignLeftIcon /></ToggleGroup.Item> <ToggleGroup.Item value="center" aria-label="Align center"><AlignCenterIcon /></ToggleGroup.Item> <ToggleGroup.Item value="right" aria-label="Align right"><AlignRightIcon /></ToggleGroup.Item> </ToggleGroup.Root> ); }

Disabled items

A disabled item dims to 50%. In single-select mode the indicator dims with the pressed item when that item or the group is disabled, and fades with the item when it becomes disabled.

The indicator is internal, so it does not follow a disabled opacity of your own on the item (for example disabled:opacity-70): it stays at 50%. To match the item’s opacity, add a rule of your own for the indicator, the span that ToggleGroup.Root renders before its items: with className="my-group" on the root, .my-group:has([data-pressed]:disabled) > span:first-child { opacity: 0.7 }. The rule must sit outside any @layer, or in @layer utilities: in @layer components it loses to the library’s opacity-50 whatever its specificity. For a group that holds another group, use .my-group:has(> [data-pressed]:disabled) > span:first-child instead: it has the same specificity, but counts only the items placed directly in the group, not those of the group inside.

When one group is nested in another, each takes its indicator state from its own items; an item wrapped in an element of its own, rather than placed directly in its group, can still take the other group’s state.

Compound Parts

PartDescription
ToggleGroup.RootContainer that manages selection state. Renders a pill-shaped muted background with inline-flex layout. In single-select mode, renders a sliding indicator behind the active item. The indicator slides to an item when it is pressed, and follows a resize at once.
ToggleGroup.ItemIndividual toggle button. Shows muted text by default, foreground text when pressed. In multiple mode, each pressed item gets its own background highlight.

Server rendering

In server HTML the pressed item draws the pill itself until the indicator is measured; no script is involved. A ::before of your own on the pressed item shows only after hydration.

Props

All compound parts are thin style wrappers. ToggleGroup.Root props are forwarded to the underlying Base UI ToggleGroup and ToggleGroup.Item props are forwarded to the underlying Base UI Toggle.

Last updated on