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.jsonUsage
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.
This field is required
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
| Prop | Type | Default | Description |
|---|---|---|---|
options | RadioGroupOption[] | — | 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.) |
cols | number | — | Number 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. |
wrap | boolean | false | `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. |
value | string | — | Controlled selected value. Pair with `onValueChange`. |
defaultValue | string | — | Uncontrolled initial value. |
onValueChange | (value: string) => void | — | Fires when the selected radio changes. |
name | string | — | Form field name (rendered as a hidden input). |
disabled | boolean | — | Disables every radio in the group. |
error | boolean | string | — | Error 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. |
caption | ReactNode | — | Helper copy shown below the group via FieldHint. Error wins when both are present. |
className | string | — | Override the wrapper layout (defaults to `grid w-full gap-2`). |
detailValues | Record<string, string> | — | Card variant only. Controlled `option.value → detail text` map for options flagged `requiresDetail`. |
defaultDetailValues | Record<string, string> | — | Card variant only. Uncontrolled initial detail-text map. |
onDetailChange | (next: Record<string, string>) => void | — | Card variant only. Fires when the revealed detail textarea changes. |
RadioGroupOption
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Value reported when the option is selected. |
label | ReactNode | — | Visible label rendered next to the radio. |
caption | ReactNode | — | Helper text — only rendered when `type="card"`. |
meta | ReactNode | — | Right-aligned secondary text (e.g. `"Free"`, `"$5"`) — card variant only. |
requiresDetail | boolean | — | Card variant only. Selecting this option reveals a required textarea beneath it, tied back with an elbow connector. Honored at `cols` 1 or 2. |
detailLabel | string | — | Label above the revealed detail textarea. Defaults to "Provide more details". |
detailPlaceholder | string | — | Placeholder for the revealed detail textarea. |
disabled | boolean | — | Disable this individual option. |