Radio group

A set of checkable buttons—known as radio buttons—where only one can be selected at a time.

Installation
$Terminal
npx shadcn@latest add https://sdk-components.thesqd.com/r/radio-group.json
Usage
Pass an `options` array. Use `variant="card"` for bordered cards with optional `description` / `meta` per option.
TSImport
import { RadioGroup } from "@/components/ui/radio-group";
TSExample
import { RadioGroup } from "@/components/ui/radio-group";

export function Example() {
  return (
    <RadioGroup
      defaultValue="comfortable"
      options={[
        { value: "default", label: "Default" },
        { value: "comfortable", label: "Comfortable" },
        { value: "compact", label: "Compact (disabled)", disabled: true },
      ]}
    />
  );
}
Composition
Anatomy of the RadioGroup primitive.
RadioGroup                       (single component — pass options as data)
├── type="default" | "card" | "pill" | "chip" | "tile"
├── options={[
│     { value, label, caption?, meta?, icon?, disabled? },
│     …
│   ]}
└── otherOption={{ value, label, caption?, placeholder? }}
    + otherValue / onOtherValueChange / defaultOtherValue

variant rendering:
  default → bare radios + labels (rows)
  card    → full-width bordered card per option (label + caption + meta)
  pill    → content-width compact card (radio + label, wraps inline)
  chip    → borderless tile (label + optional meta line, wraps inline)
  tile    → large icon-led card (icon + label + caption, centered)

For multi-select, see <MultiSelect /> in @/components/ui/multi-select
(documented on the /multi-select page) — same flat options shape, with
type="cards" or type="chip-group".

Slot exports — RadioGroupItem, RadioGroupCard, RadioGroupCardLabel,
RadioGroupCardDescription — remain as the escape hatch for fully custom
layouts.
Basic
Card group
Each option is a bordered card with a radio on the left. Border tints primary when selected.
Card group — 2-column wrap
Same card treatment, laid out in a responsive 2-column grid that collapses to a single column on narrow screens.
Card group with detail field
Flag any card option with `requiresDetail: true` (plus an optional `detailLabel`) and selecting it reveals a required textarea nested directly beneath it — indented and tied back with an elbow connector. Pass `detailValues` / `onDetailChange` to read the text (a `value → text` map). Card variant only, at `cols` 1 or 2.
Card group — content-width wrap
Cards size to their contents and flow left-to-right, wrapping onto new rows as the container narrows.
Pill group that wraps with Other
`type="pill"` with a circular radio indicator before each label. Pass `wrap` so pills size to their content and flow onto new rows; the trailing `otherOption` morphs into an inline input when selected — blur with a blank value to clear, or a filled value to collapse it to a chip showing the typed text. Mirrors the multi-select chip group.
Pill group — 3 columns with Other
Same pill treatment, but pass `cols={3}` for a fixed three-column grid of equal-width pills instead of `wrap`. In columns mode the radio dot pins to the left and the label centers; the inline `otherOption` fills its column when selected.
Chip group
Compact grid of options; the whole chip is clickable and the radio is visually hidden.

Select shoe size

With Other input
Selecting the last option reveals an Input so the user can enter a custom value.

How did you hear about us?

Card group with Other
The last card reveals an Input for a custom answer, right inside the card.

What team are you on?

Chip group — large

Choose a plan

Plan cards
Icon-led vertical cards; the radio input is hidden and the whole card toggles selection.
Error state
Pass `error` to the RadioGroup. A boolean marks items invalid (red ring); a string also renders the inline `text-xs mt-1.5 text-red-500` message — same treatment as RichTextEditor and FileUpload.
With caption
Pass `caption` to render helper copy under the label. Error wins when both are present.

We'll only use this for important project updates.

API Reference
Props exposed by the RadioGroup primitive and its items.

RadioGroup

PropTypeDefaultDescription
optionsRadioGroupOption[]List of options to render. Without this prop, RadioGroup stays compound (drop in `<RadioGroupItem>` children manually).
type"default" | "card" | "pill" | "chip" | "tile""default"Variant. `default` is bare radios. `card` wraps each option in a bordered row with optional `meta` / `description`. `pill` and `chip` are single-row h-9 selectors. `tile` is a square tile with a centered label. (Legacy alias: `variant` — still accepted.)
colsnumberNumber of columns. For `card` / `tile`, omitting it wraps responsively (auto-fill, min 16rem per card). For `pill`, sets a fixed grid of equal-width pills (e.g. `cols={2}`); ignored when `wrap` is set.
wrapbooleanfalse`pill` only. Flow pills in a wrapping inline row sized to each label instead of a fixed grid. Wins over `cols`, and is the default for `pill` when neither is set.
valuestringControlled selected value. Pair with `onValueChange`.
defaultValuestringUncontrolled initial value.
onValueChange(value: string) => voidFires when the selected radio changes.
namestringForm field name (rendered as a hidden input).
disabledbooleanDisables every radio in the group.
errorboolean | stringError state. `true` marks items invalid (red ring); a string also renders the inline `text-xs mt-1.5 text-red-500` message below the group.
captionReactNodeHelper copy shown below the group via FieldHint. Error wins when both are present.
classNamestringOverride the wrapper layout (defaults to `grid w-full gap-2`).
detailValuesRecord<string, string>Card variant only. Controlled `option.value → detail text` map for options flagged `requiresDetail`.
defaultDetailValuesRecord<string, string>Card variant only. Uncontrolled initial detail-text map.
onDetailChange(next: Record<string, string>) => voidCard variant only. Fires when the revealed detail textarea changes.

RadioGroupOption

PropTypeDefaultDescription
valuestringValue reported when the option is selected.
labelReactNodeVisible label rendered next to the radio.
captionReactNodeHelper text — only rendered when `type="card"`.
metaReactNodeRight-aligned secondary text (e.g. `"Free"`, `"$5"`) — card variant only.
requiresDetailbooleanCard variant only. Selecting this option reveals a required textarea beneath it, tied back with an elbow connector. Honored at `cols` 1 or 2.
detailLabelstringLabel above the revealed detail textarea. Defaults to "Provide more details".
detailPlaceholderstringPlaceholder for the revealed detail textarea.
disabledbooleanDisable this individual option.