toggle-group
A set of two-state buttons that can be toggled on or off.
Philosophy
When multiple toggles share context, they become a group. ToggleGroup manages mutual exclusivity (single mode) or multi-selection, keeping the state logic out of your components. Built on @gentleduck/primitives/toggle-group with roving focus keyboard navigation - arrow keys move between items, Space/Enter toggles them.
How It's Built
Installation
npx @gentleduck/cli add toggle-group
npx @gentleduck/cli add toggle-group
Usage
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
<ToggleGroup type="single">
<ToggleGroupItem value="a">A</ToggleGroupItem>
<ToggleGroupItem value="b">B</ToggleGroupItem>
<ToggleGroupItem value="c">C</ToggleGroupItem>
</ToggleGroup>
Examples
Default
Outline
Single
Small
Large
Disabled
Keyboard Navigation
| Key | Description |
|---|---|
Tab | Moves focus into the toggle group (focuses the active or first item) |
ArrowRight | Moves focus to the next item |
ArrowLeft | Moves focus to the previous item |
ArrowDown | Moves focus to the next item |
ArrowUp | Moves focus to the previous item |
Home | Moves focus to the first item |
End | Moves focus to the last item |
Space / Enter | Toggles the focused item |
RTL Support
Set the direction once with DirectionProvider (or <html dir="rtl">) and ToggleGroup follows it, including the joined corners and flipped Left/Right arrow keys. No dir prop needed.
Motion
Use MotionToggleGroup and MotionToggleGroupItem for a sliding active-item indicator and tap press feedback powered by motion. A shared background indicator slides between items via layoutId as the pressed state changes, and each item presses to 0.97 on tap.
motion package. Use MotionToggleGroup + MotionToggleGroupItem instead of ToggleGroup + ToggleGroupItem. Same props. The regular components are perfectly fine - this is an optional enhancement.API Reference
ToggleGroup
| Prop | Type | Default | Description |
|---|---|---|---|
type | 'single' | 'multiple' | (required) | Determines selection behavior. 'single' allows one toggle at a time; 'multiple' allows multiple |
value | string | string[] | - | Controlled selected value(s) |
defaultValue | string | string[] | - | Uncontrolled initial selected value(s) |
onValueChange | (value: string | string[]) => void | - | Callback invoked when the value changes |
variant | 'default' | 'outline' | 'default' | Visual style variant for the toggles (passed to items via context) |
size | 'default' | 'sm' | 'lg' | 'default' | Size variant for the toggles (passed to items via context) |
disabled | boolean | false | Disables all items in the group |
rovingFocus | boolean | true | Whether to use roving focus for keyboard navigation |
loop | boolean | true | Whether keyboard navigation should loop at boundaries |
orientation | 'horizontal' | 'vertical' | - | The orientation of the group for arrow key navigation |
dir | 'ltr' | 'rtl' | - | Text direction. Resolved by primitives useDirection (dir prop -> DirectionProvider -> <html dir>). |
className | string | - | Additional CSS class names to apply |
children | React.ReactNode | - | ToggleGroupItem elements to render inside the group |
ref | React.Ref<HTMLDivElement> | - | Ref forwarded to the underlying div element |
ToggleGroupItem
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | (required) | The unique value identifying this toggle item |
variant | 'default' | 'outline' | - | Visual style variant for this item (overrides group context) |
size | 'default' | 'sm' | 'lg' | - | Size variant for this item (overrides group context) |
disabled | boolean | false | Disables this specific item |
className | string | - | Additional CSS class names to apply |
children | React.ReactNode | - | Content rendered inside the toggle item |
ref | React.Ref<HTMLButtonElement> | - | Ref forwarded to the underlying button element |
Data Attributes
| Attribute | Values | Description |
|---|---|---|
data-state | "on" | "off" | The pressed state of the item |
data-disabled | Present when disabled | Whether the item is disabled |
MotionToggleGroup
Wraps ToggleGroup in a LayoutGroup (scoped to a unique group ID) so its items can share the layoutId-based sliding indicator. Has no entrance animation of its own. Requires the motion package.
| Prop | Type | Default | Description |
|---|---|---|---|
...props | ToggleGroupProps | - | All props from ToggleGroup are supported |
MotionToggleGroupItem
Adds whileTap press feedback (scale 0.97). When pressed (data-state="on"), renders a shared layoutId background indicator that slides smoothly between items using a springSmooth transition. Requires the motion package.
| Prop | Type | Default | Description |
|---|---|---|---|
...props | Omit<ToggleGroupItemProps, 'onDrag' | 'onDragStart' | 'onDragEnd' | 'onAnimationStart'> | - | All props from ToggleGroupItem except motion event handlers |