Easy Shadcn
Components

Choice Group

Choice Group gives one stable items and value API to related choices. Selection has two modes—single or multiple—and presentation has three forms—radio, checkbox, or toggle. The public type permits only the four combinations with coherent semantics.

Single · radio

Multiple · checkbox

Single · toggle

Installation

With the @easy-shadcn namespace configured:

pnpm dlx shadcn@latest add @easy-shadcn/choice-group

Or install via the full URL:

pnpm dlx shadcn@latest add https://easy-shadcn.vercel.app/r/choice-group.json

The shadcn Radio Group, Checkbox, and Toggle Group primitives are installed automatically.

Four valid combinations

SelectionPresentationValue shapeConfiguration
SingleRadiostring | undefinedDefault: no mode props required.
MultipleCheckboxstring[]selectionMode="multiple"
SingleTogglestring | undefinedpresentation="toggle"
MultipleTogglestring[]selectionMode="multiple" presentation="toggle"

multiple + radio and single + checkbox are type errors. They are not runtime aliases with surprising behavior.

Radio and checkbox

const items = [
  { value: "email", label: "Email", description: "Daily digest" },
  { value: "sms", label: "SMS", disabled: true },
]

<ChoiceGroup items={items} defaultValue="email" />

<ChoiceGroup
  items={items}
  selectionMode="multiple"
  defaultValue={["email"]}
/>

The first example defaults to single + radio. The second defaults to multiple + checkbox. Changing between them keeps the same item schema; only the selection discriminant and value shape change.

Toggle presentation

Toggle presentation supports both selection modes and the shadcn Toggle Group visual controls.

<ChoiceGroup
  aria-label="Density"
  items={densityItems}
  presentation="toggle"
  orientation="horizontal"
  variant="outline"
  spacing={0}
  defaultValue="comfortable"
/>

Single toggle selection may become empty when the pressed item is toggled off, so onValueChange receives undefined. Multiple toggle selection always returns an array.

Controlled state

All four combinations use value, defaultValue, and onValueChange. Choice Group deliberately removes Base UI event-detail objects from its callback; callers receive only the next domain value, so switching presentation does not change handler shape.

<ChoiceGroup
  items={channels}
  selectionMode="multiple"
  value={channelsValue}
  onValueChange={setChannelsValue}
/>

An explicitly supplied value={undefined} remains controlled for single selection. In controlled mode, interaction requests a change but visual selection moves only after the parent updates value.

Choice Group owns the generated root structure, presentation role, orientation, disabled semantics, and primitive state markers. Root children, raw HTML, polymorphic rendering, native onChange, and conflicting ARIA/data state attributes are rejected at the type boundary and ignored at runtime. Caller-owned group naming (aria-label / aria-labelledby), refs, events, classes, ordinary styles, and unrelated data-* attributes still reach the active root. In toggle presentation, spacing remains the source of truth for both data-spacing and the internal --gap variable.

Labels and descriptions

Each item renders its label and optional description with stable aria-labelledby / aria-describedby wiring. ariaLabel replaces the visible label as the accessible name when product copy and spoken copy must differ.

Group-level class fields merge first; item-level fields merge after them.

FieldTypeDescription
valuestringStable unique option value.
labelReactNodeVisible option label.
descriptionReactNodeOptional helper text.
ariaLabelstringOptional explicit accessible name.
disabledbooleanDisables this option. A disabled group still wins.
optionClassNameClassValueRadio/checkbox row or toggle item class.
controlClassNameClassValueRadio/checkbox control; also merged into the toggle item.
labelClassNameClassValueVisible label class.
descriptionClassNameClassValueDescription class.

Props

PropTypeDefaultDescription
itemsChoiceGroupItem[]requiredHomogeneous option definitions. Empty arrays render an empty semantic group.
selectionMode"single" | "multiple""single"Selects scalar or array value semantics.
presentation"radio" | "checkbox" | "toggle"Radio for single; checkbox for multipleVisual and interaction adapter. Invalid mode pairs are rejected by TypeScript.
valuestring | string[] | undefined-Controlled value, narrowed by the discriminated mode pair.
defaultValuestring | string[] | undefined-Initial uncontrolled value, narrowed by the mode pair.
onValueChange(value) => void-Next scalar or array only; primitive event details stay private.
disabledbooleanfalseDisables every option.
orientation"vertical" | "horizontal""vertical"Layout and keyboard orientation.
variant"default" | "outline""default"Toggle presentation only.
size"default" | "sm" | "lg""default"Toggle presentation only.
spacingnumber2Toggle presentation only. Use 0 for joined buttons.
optionClassNameClassValue-Class merged into every option wrapper/control.
controlClassNameClassValue-Class merged into every primitive control.
labelClassNameClassValue-Class merged into every label.
descriptionClassNameClassValue-Class merged into every description.
classNameClassValue-Root class. Compatible caller-owned group props are forwarded.

Legacy group components

RadioGroup and CheckboxGroup remain installable for compatibility, but are soft-deprecated. New work should use Choice Group. They receive no new capabilities; migrate when touching nearby code. There is no separate Toggle Group Compose registration—the shadcn primitive is installed as Choice Group's presentation dependency.

Use the underlying primitives directly for heterogeneous option markup, custom indicators, per-checkbox indeterminate state, a parent “select all” checkbox, or primitive-specific event details and form behavior.

On this page