Toggle Group
Chips that toggle, in single or multiple mode, wrapping across rows. A member of the field family: it owns its label and its error message, takes required, and posts through name. Two sizes, one per-item color variant.
className styles the wrapper
ToggleGroup renders a wrapper around the chip row so it can hold the group label and the error message, the same as Radio Group and Select. className styles that wrapper, not the row. On ToggleGroupItem it styles the chip. Every other prop passes through to the matching Radix part.
Single
mode defaults to ToggleGroupMode.Single: value is a string and onValueChange hands back a string. Clicking the pressed chip deselects it and reports an empty string. That return to empty is the reason toggle-group sits beside Radio Group rather than inside it — a radio group cannot be emptied by the user. Each item takes an optional 16px leading icon on Button's slot rule.
Filtering by food.
Filtering by food.
Multiple and max
ToggleGroupMode.Multiple switches value to an array and adds max. At the cap every unpressed chip takes the disabled look and stops toggling, while pressed chips stay live so the user can always back out of a pick. The registry renders no counter and no hint: the label says the limit. A capped chip and a disabled chip are deliberately indistinguishable — both are unavailable right now, and the label already explains why.
Required
required means the group must hold a value. The component blocks the deselect that would empty it, in both modes, and the field label renders its marker. This is the field family's meaning of required. It is not a submit-time check: a group mounted empty is reachable, and a submit while it is still empty is the app's validation error, shown through error.
Pick a pace before generating
Pick a pace before generating
Amber
variant on an item takes ToggleGroupItemVariant.Amber, which swaps the pressed fill from --primary to amber-400 with neutral-950 text, Button's amber row. Only the pressed fill changes: an unpressed amber chip is the same grey as every other chip, and it toggles like every other one. It marks an option that needs a step the label names, such as a visa or a permit. A chip that fires an action and never toggles is the app's to compose from a Button; the registry ships no such rule.
Sizes and states
size sits on the group, not on the item: ToggleGroupSize.Default is 36px and ToggleGroupSize.Small is 32px, matching Input and Button. Chips are fully rounded and wrap across rows. A chip rests on --secondary with its border in the same color, so hover shows up as the border stepping to --accent; pressed fills --primary and steps one shade lighter on hover. Feedback lives in the border and the fill, never in elevation. A held press draws Button's tight 2px ring with no scale and no translate (ADR 0003).
Not a segmented control, not a badge
Tabs draw a shared track and switch the panel underneath; a segmented control is that same shape borrowed for a value. Toggle Group has no track, no fixed row, and no panel: the chips wrap, multiple mode exists, and single mode returns to empty. If the choice navigates, it is Tabs. If it answers a question, it is Toggle Group. And a chip the user cannot press is a Badge — a read-only summary of activity types, or the chosen values echoed back on a review screen, is Badge and not a disabled group. There is no separate chip primitive.
Forms
name puts the raw values into the surrounding form's FormData through hidden inputs: one in single mode, one per pressed chip in multiple mode, and none at all while the group is empty. value with onValueChange hands the selection to the app and defaultValue leaves it with the component — the props a form-state library drives, and the registry binds to none of them itself (ADR 0007).
Posting 1 hidden input named interests.
Posting 1 hidden input named interests.
Motion
The fill and the border swap on a CSS transition at --motion-fast, and that is the whole of it. Toggle Group has no motion dependency — chips added while the group is mounted appear with no enter animation, on Badge's precedent, and an app that mounts options dynamically wraps them itself. The animated error message below the row comes from the field item.
Accessibility
Single mode exposes role="radiogroup" with role="radio" chips carrying aria-checked; multiple mode exposes a group of buttons carrying aria-pressed. The group is named by its label through aria-labelledby, takes aria-required in single mode, and an error sets aria-invalid and links the message through aria-describedby. The row is one tab stop. Tab lands on the pressed chip, Left and Right move and wrap at the ends, Home and End reach them directly, Space and Enter toggle, and Up and Down do nothing — a wrapped row has no vertical order to follow. Disabled and capped chips leave the arrow order. The focus ring is Button's keyboard-only offset ring.