Skip to content

Toggle Group

Compound toggle button group with keyboard navigation for single or multiple selection.

Preview

Installation

$pnpm exec dgadd add ui/toggle-group
[Installs to]src/components/ui/toggle-group[Item]ui/toggle-group

dgadd ships in @diffgazer/add on npm: pnpm add -D @diffgazer/add puts it on pnpm exec, and npx @diffgazer/add runs it without the install.

UI components require Tailwind CSS v4. Local copy mode imports src/styles/styles.css; package mode uses @diffgazer/ui CSS from the installed package.

Usage

tsx
"use client";import { useState } from "react";import { ToggleGroup } from "@/components/ui/toggle-group";export default function ToggleGroupDefault() {  const [value, setValue] = useState<string | null>("all");  return (    <ToggleGroup label="File change filter" value={value} onChange={setValue}>      <ToggleGroup.Item value="all">All</ToggleGroup.Item>      <ToggleGroup.Item value="added">Added</ToggleGroup.Item>      <ToggleGroup.Item value="modified">Modified</ToggleGroup.Item>      <ToggleGroup.Item value="deleted">Deleted</ToggleGroup.Item>    </ToggleGroup>  );}

Examples

Variants

Preview

With Counts

Preview

Multiple Selection

Preview

Sizes

Preview

Vertical

Preview

Disabled

Preview

API Reference

ToggleGroup

NameTypeDefaultDescription
selectionMode"single" | "multiple""single"Switches between radio-style single selection and pressed-button-style multiple selection. Switches value/onChange/defaultValue from string|null to readonly string[].
valuestring | null | readonly string[]—Controlled selected value(s). string|null for single mode, readonly string[] for multiple.
defaultValuestring | null | readonly string[]null (single) | [] (multiple)Initial selected value(s) for uncontrolled mode.
onChange((value: string | null) => void) | ((value: readonly string[]) => void)—Fired when the selected value(s) change.
allowDeselectbooleanfalseSingle mode only. When true, clicking the active item deselects it (allowing a null value).
disabledbooleanfalseDisables the entire group.
size"sm" | "md""sm"Item density token. "sm" is 36px, rising to a 44px target on coarse pointers; "md" is always a 44px target.
variant"default" | "bracket" | "pill" | "underline""default"Item style: default outlines, bracket adds [ ], pill fills, underline rules.
orientation"horizontal" | "vertical""horizontal"Layout axis and arrow-key navigation direction.
wrapbooleantrueWhen true, arrow navigation wraps and horizontal items flex-wrap.
highlightedstring | null—Controlled highlighted (focused) value for cross-component navigation.
onHighlightChange(value: string | null) => void—Fired when the highlighted value changes.
onNavigationBoundaryReached(direction: "previous" | "next", event: KeyboardEvent, key: string) => void—Fired when arrow navigation reaches the first/last item with wrap disabled.
labelstring—Accessible name for the group container.
aria-labelledbystring—ID of the element labelling the group.
namestring—Single mode only. Form field name; renders a hidden input for native form submission.
childrenrequiredReactNode—ToggleGroup.Item children.

ToggleGroup.Item

NameTypeDefaultDescription
valuerequiredstring—Stable identifier matched against the group value.
countnumber—Optional trailing count rendered as [label count].
disabledbooleanfalseDisables this item only; removed from arrow navigation and focus.
childrenrequiredReactNode—Item label.

Data attributes

AttributeApplies toValuesDescription
data-stateToggleGroup.Item"on" | "off"Reflects whether the item is selected.
data-highlightedToggleGroup.Itempresent when highlightedMarks the roving-focus item.
data-valueToggleGroup.Itemitem valueExposes the item value used for selection and indicator positioning.
data-variantToggleGroup"default" | "pill" | "underline" | "bracket"Reflects the visual variant on the group root.
data-orientationToggleGroup"horizontal" | "vertical"Reflects the layout and arrow-key axis.

Accessibility

Keyboard Navigation

Arrow keys move focus between toggle items with wrapping. Single mode follows focus; multiple mode and allow-deselect single mode use button semantics and toggle with Enter or Space.

KeyAction
ArrowRight / ArrowDownMoves focus to the next enabled item; wraps when wrap is true.
ArrowLeft / ArrowUpMoves focus to the previous enabled item; wraps when wrap is true.
Home / EndMoves focus to the first or last enabled item.
Enter / SpaceToggles the focused item in multiple mode or allow-deselect single mode; native radio-style single mode changes selection during arrow navigation.

With keyboard navigation

Preview

Notes

Requires @diffgazer/keys (package mode)

ToggleGroup's arrow-key navigation imports from the required @diffgazer/keys peer. Package consumers must install @diffgazer/keys with @diffgazer/ui; install both from npm (npm install @diffgazer/ui @diffgazer/keys), or take the component from the live registry instead (npx shadcn add https://r.b4r7.dev/r/ui/toggle-group.json). Importing @diffgazer/ui/components/toggle-group without keys fails at module load with an error naming the missing @diffgazer/keys package. Copy/dgadd consumers do not need the package — copy mode rewrites the keyboard hooks to local source.

Compound Component

ToggleGroup uses compound sub-components (ToggleGroup.Item) rather than a data array. Each item accepts children for the label and an optional count prop that renders as [label count].

Composition Contract

Use ToggleGroup.Item as an explicit child in the ToggleGroup JSX tree. Custom item UI belongs inside ToggleGroup.Item. Components that create items internally from an opaque wrapper are not part of the current public contract.

Keyboard Navigation

Arrow keys move roving focus with wrapping by default. In single mode, selection follows focus unless allowDeselect is enabled; in multiple mode, arrow keys move focus and Enter/Space toggles the focused item. Highlight state can be controlled externally via highlighted and onHighlightChange props. Use onNavigationBoundaryReached for composite focus handoff when wrap is false.

Role semantics

Single mode without allowDeselect renders as radiogroup/radio with aria-checked and allows at most one active choice. Initial selection is optional: defaultValue=null starts with no radio checked. Multiple mode and allow-deselect single mode render as group plus button-style items with aria-pressed because each item is independently toggleable.

Source

Install via CLI: pnpm exec dgadd add ui/toggle-group. Keyboard hooks are included as standalone copies. For the full experience, use --integration keys.

Highlighted source loads after this disclosure opens. Browse the source repository.