Skip to Content
Puzzle

Toggle Group

Toggle groups let users select one or more options from a related set.

Use the ToggleGroup component to present a set of related toggle buttons. Choose single selection for mutually exclusive options or multiple selection when users can choose several.

Import

import { ToggleGroup } from "@puzzle-ui/ui";

Basic usage

Add a ToggleGroup.Item for each option. In single-selection mode, the default, selecting an item deselects the previously selected item. Selecting the active item again clears the selection.

390 × 160

Each item is rendered as a Button and exposes its selected state with aria-pressed.

Selection modes

Use the default type="single" mode when only one option can be active. Set type="multiple" to let users toggle options independently.

390 × 160

Controlled and uncontrolled state

Use defaultValue to set the initial selection and let the group manage subsequent changes. Use value with onValueChange when the parent needs to control selection.

const [alignment, setAlignment] = useState<string | undefined>("left");

return (
  <ToggleGroup
    value={alignment}
    onValueChange={setAlignment}
    aria-label="Text alignment"
  >
    <ToggleGroup.Item value="left">Left</ToggleGroup.Item>
    <ToggleGroup.Item value="center">Center</ToggleGroup.Item>
    <ToggleGroup.Item value="right">Right</ToggleGroup.Item>
  </ToggleGroup>
);

In single mode, onValueChange receives the selected value or undefined when the selection is cleared. In multiple mode, it receives the full array of selected values.

<ToggleGroup
  type="multiple"
  value={formats}
  onValueChange={setFormats}
  aria-label="Text formatting"
>
  <ToggleGroup.Item value="bold">Bold</ToggleGroup.Item>
  <ToggleGroup.Item value="italic">Italic</ToggleGroup.Item>
</ToggleGroup>

Size

Use the size prop to set the size of every item in the group. It accepts the same values as Button: xs, sm, md, lg, and icon. The default is md.

390 × 160

Required selection

Use required to prevent users from clearing the final selected value. It does not create an initial selection; provide defaultValue or value if an option must start selected.

<ToggleGroup required defaultValue="monthly" aria-label="Billing period">
  <ToggleGroup.Item value="monthly">Monthly</ToggleGroup.Item>
  <ToggleGroup.Item value="yearly">Yearly</ToggleGroup.Item>
</ToggleGroup>

Orientation and layout

Use orientation to arrange items horizontally or vertically. The default is horizontal.

390 × 260

Set attached to join adjacent items, or fullWidth to make the group fill its container and let the items share the available width.

390 × 160

Use spacing to set the gap between items. Numbers are interpreted as pixels; strings are passed as CSS values. The default is the Puzzle spacing token (with a 0.5rem fallback). Spacing has no effect when attached is enabled.

Disabled

Set disabled on the group to disable all items, or set it on an individual item to disable only that option. Disabled items are skipped during keyboard navigation.

<ToggleGroup defaultValue="read" aria-label="Permission">
  <ToggleGroup.Item value="read">Read</ToggleGroup.Item>
  <ToggleGroup.Item value="write">Write</ToggleGroup.Item>
  <ToggleGroup.Item value="admin" disabled>
    Admin
  </ToggleGroup.Item>
</ToggleGroup>

API

Group props

PropTypeDefaultDescription
type"single" | "multiple""single"Whether one or multiple items can be selected.
valuestring | null (single); string[] (multiple)—Controlled selection. Use onValueChange to update it.
defaultValuestring (single); string[] (multiple)—Initial selection for an uncontrolled group.
onValueChange(value: string | undefined) => void (single); (value: string[]) => void (multiple)—Called when the selection changes.
orientation"horizontal" | "vertical""horizontal"Layout direction and arrow-key navigation direction.
size"xs" | "sm" | "md" | "lg" | "icon""md"Size applied to every item in the group.
dir"ltr" | "rtl""ltr"Text direction; reverses horizontal arrow-key navigation in RTL.
disabledbooleanfalseDisables all items in the group.
loopbooleantrueWraps arrow-key focus navigation from the last item to the first and vice versa.
requiredbooleanfalsePrevents clearing the final selected item; does not force an initial selection.
attachedbooleanfalseJoins adjacent item borders and removes inner corner radii.
fullWidthbooleanfalseMakes the group fill its container and lets its items flex to fill the available space.
spacingnumber | stringPuzzle spacing tokenGap between items. Numbers are interpreted as pixels.
classNamestring—Additional CSS class names.
childrenReactNode—ToggleGroup.Item elements displayed in the group.

The group also accepts standard <div> props, including aria-* and data-* attributes. Its role defaults to "group" and can be overridden.

Item props

PropTypeDefaultDescription
valuestring—Unique value identifying the item within its group.
disabledbooleanfalseDisables this item.
childrenReactNode—Item label or content.
classNamestring—Additional CSS class names.

Items also accept standard native Button props, such as aria-label, onClick, and data-* attributes. The group manages the Button's type, variant, size, disabled, tabIndex, and aria-pressed props.

TypeScript types

The component exports ToggleGroupProps, ToggleGroupSingleProps, ToggleGroupMultipleProps, ToggleGroupItemProps, ToggleGroupOrientation, and ToggleGroupSize. ToggleGroupSize is the same type as ButtonSize. ToggleGroupProps is a discriminated union: type="multiple" selects the array-based value and callback types; single selection is the default.

Accessibility

Group and item semantics

ToggleGroup uses the group role by default. Give each group an accessible name with aria-label or aria-labelledby. Each item is a native Button with aria-pressed reflecting its selected state.

Keyboard interaction

Only one item in the group is in the tab order at a time. Use the arrow keys to move focus between enabled items: Left and Right for horizontal groups, Up and Down for vertical groups. In right-to-left horizontal groups, Left and Right navigation is reversed. Home and End move focus to the first and last enabled items. Navigation wraps by default; set loop={false} to stop at the ends. Enter or Space activates the focused item using native Button behavior.

Moving focus does not change the selection. Selection changes when an item is activated.

Disabled state

Disabled items cannot be activated and are skipped during keyboard navigation. Disabling the group disables every item.

;