v0.5

Radio Group

Single-select form group with shared label, helper text, and error footer.

For personal projects.

For freelancers and small teams.

For larger teams with shared workspaces.

Description

RadioGroup owns the selected value for a set of mutually-exclusive options. It wraps Radix UI's RadioGroup.Root (which manages roving focus and arrow-key navigation) and the in-house Field (which provides a labelled layout with description and error slots). variant and size cascade through RadioContext so every child Radio stays visually consistent.

Reach for it when two to six options should all be visible at once: pricing tier, theme picker, sort order. Pair each Radio with a Label + LabelDescription inside a FieldContent block so screen readers announce both the option and its explanation.

Don't use it for more than six options; use Select so the list collapses. Don't use it for multi-select; that's CheckboxGroup. Don't use it for a single yes/no; use Switch or Checkbox.

Installation

pnpm dlx @create-ui/cli add radio-group

Anatomy

A RadioGroup wraps multiple Radio items, each paired with its label content inside a FieldContent block. The group itself manages selection; Field provides the layout.

<RadioGroup>
  <Radio value="…" id="…" />
  <FieldContent>
    <LabelMain>
      <Label htmlFor="…" />
      <LabelDescription />
    </LabelMain>
  </FieldContent>
  {/* …repeat per option… */}
  <FieldFooter />
</RadioGroup>

Usage

import { Label } from "@/components/ui/label"
import { Radio } from "@/components/ui/radio"
import { RadioGroup } from "@/components/ui/radio-group"
<RadioGroup defaultValue="a">
  <Radio value="a" id="a" />
  <Label htmlFor="a">Option A</Label>
  <Radio value="b" id="b" />
  <Label htmlFor="b">Option B</Label>
</RadioGroup>

Examples

Variants

variant="primary" is the default. variant="neutral" reads quietly inside dense surveys; variant="danger" repaints the footer in text-error-base and forwards invalid to the underlying Field.

Pick one to continue.

Sizes

size cascades to every child Radio and Label through context. Pick xs for dense settings and md when the group anchors a step in a form.

Label scales from the group context.

Label scales from the group context.

Label scales from the group context.

Placement

placement="left" (default) puts the radio before its label. placement="right" flips the row direction for each option, which is useful when aligning radios against a right-hand edge.

Default — radio before label.

Mirror layout — radio after label.

Controlled

Drive the selection from state by combining value and onValueChange. Use this when the parent owns the form value or needs to react to changes.

Selected: system

Accessibility

RadioGroup delegates focus and ARIA to Radix UI's RadioGroup.Root. Focus management is roving: only the checked (or first) radio is in the tab order, and arrow keys move selection without leaving the group.

KeyDescription
TabMoves focus into the group at the checked (or first) item.
SpaceSelects the focused radio.
ArrowDown / ArrowRightMoves focus to and selects the next radio in the group.
ArrowUp / ArrowLeftMoves focus to and selects the previous radio in the group.

ARIA notes:

  • The root renders with role="radiogroup".
  • Setting invalid cascades through Field so descendants pick up aria-invalid styling.
  • disabled on the group disables every child radio; pass disabled on individual children to remove only those items from the tab order.

Styling

Tailwind override: pass className to merge Tailwind classes on the Radix root, and fieldClassName to style the inner Field (useful for switching the field layout from horizontal to vertical or adjusting gap):

<RadioGroup className="w-full" fieldClassName="flex-col items-stretch gap-4" />

Data slots and attributes: the component sets these for CSS targeting:

  • data-slot="radio-group" on the Radix root.
  • data-variant="<variant>", data-size="<size>", data-placement="<placement>" on the root.
  • Inherited from Field: data-slot="field", data-size, data-invalid, data-disabled, data-loading.
  • variant="danger" restyles [data-slot="field-footer"] (and any nested <a>) to text-error-base.

Target the error state in CSS:

[data-slot="radio-group"][data-variant="danger"] [data-slot="field-footer"] {
  color: var(--color-error-base);
}
  • Radio: the required child item; declares its own value.
  • CheckboxGroup: multi-select alternative.
  • Select: collapse the list when there are more than six options.
  • Field: the lower-level form-field wrapper this component composes.

API Reference

RadioGroup

A single-select form group. Extends React.ComponentProps<typeof Radix.RadioGroup.Root>, so any prop the Radix primitive accepts (value, defaultValue, onValueChange, name, required, …) is forwarded.

Props

PropTypeDefaultDescription
valuestring-Controlled selected value.
defaultValuestring-Uncontrolled initial selected value.
onValueChange(value: string) => void-Fires when the selected value changes via user interaction.
namestring-Form field name submitted with the parent form.
requiredbooleanfalseMarks the group as required in a form.
disabledboolean-Disables every child radio and cascades a disabled style to label and description.
invalidboolean-Marks the group as invalid; cascades aria-invalid styling through Field.
childrenReact.ReactNode-One or more Radio items, typically paired with FieldContent + Label blocks.
classNamestring-Tailwind classes merged on the Radix root.
fieldClassNamestring-Tailwind classes merged on the inner Field wrapper. (advanced)

Variants

VariantOptionsDefaultDescription
variant"primary" "neutral" "danger""primary"Semantic intent. "danger" cascades to each child radio and restyles the footer.
size"xs" "sm" "md""md"Cascades to each child radio and label via context.
placement"left" "right""left"Row direction for each option. "right" flips the visual order while preserving DOM order.