1
ComponentsForm Controls

Radio Group

Single-select controls for choosing exactly one option from a short visible list.

Purpose

Use Radio Group when exactly one option must be selected from a short, visible set.

Single choice

import { Radio, RadioGroup } from "@/components/ui/radio-group";
import { Label } from "@/components/ui/label";

export function Example() {
  return (
    <RadioGroup aria-label="Digest frequency" defaultValue="weekly">
      <Label><Radio value="weekly" />Weekly</Label>
      <Label><Radio value="monthly" />Monthly</Label>
    </RadioGroup>
  );
}

Installation

With the Siteplane registry alias configured, add the component and import the installed source from your app.

CLI

pnpm dlx shadcn@4.16.1 add @siteplane/radio-group

Import

import { Radio, RadioGroup } from "@/components/ui/radio-group";
import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field";
import { Form } from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";

Need the machine-readable source metadata? View registry JSON.

Usage

Use Radio Group for one choice from a visible set. Keep each Radio paired with readable text.

import { Radio, RadioGroup } from "@/components/ui/radio-group";
import { Label } from "@/components/ui/label";

export function Example() {
  return (
    <RadioGroup aria-label="Digest frequency" defaultValue="weekly">
      <Label><Radio value="weekly" />Weekly</Label>
      <Label><Radio value="monthly" />Monthly</Label>
    </RadioGroup>
  );
}

Examples

Sizes, states, field integration, option descriptions, card style selection, horizontal layout and form integration are props and compositions of the same primitive.

Sizes

{/* Per-radio sizes */}
<RadioGroup aria-label="Radio sizes" className="flex-row items-center">
  <Radio aria-label="Small radio" size="sm" value="sm" />
  <Radio aria-label="Default radio" value="default" />
  <Radio aria-label="Large radio" size="lg" value="lg" />
  <Radio aria-label="Extra large radio" size="xl" value="xl" />
</RadioGroup>

{/* Group-level size, inherited by every Radio */}
<RadioGroup aria-label="Group-level size" size="lg">
  <Radio aria-label="First inherited large radio" value="first" />
  <Radio aria-label="Second inherited large radio" value="second" />
</RadioGroup>

States

{/* Unselected and selected */}
<RadioGroup aria-label="Plan states" defaultValue="selected">
  <Label><Radio value="unselected" />Unselected</Label>
  <Label><Radio value="selected" />Selected</Label>
</RadioGroup>

{/* Disabled */}
<RadioGroup aria-label="Disabled plan states" defaultValue="selected">
  <Label><Radio disabled value="unselected" />Disabled</Label>
  <Label><Radio disabled value="selected" />Disabled selected</Label>
</RadioGroup>

{/* Invalid */}
<RadioGroup aria-invalid aria-label="Invalid plan states">
  <Label><Radio aria-invalid value="invalid" />Invalid</Label>
</RadioGroup>

With field and error

const labelId = useId();

<Field>
  <FieldLabel id={labelId}>Workspace plan</FieldLabel>
  <RadioGroup aria-invalid aria-labelledby={labelId}>
    <Label><Radio aria-invalid value="starter" />Starter</Label>
    <Label><Radio aria-invalid value="pro" />Pro</Label>
  </RadioGroup>
  <FieldError>Please select a plan before continuing.</FieldError>
</Field>

With descriptions

<RadioGroup aria-label="Sync mode" defaultValue="auto">
  <label className="flex items-start gap-2.5">
    <Radio
      aria-describedby={descriptionId}
      aria-labelledby={labelId}
      value="auto"
    />
    <span className="grid gap-1">
      <span id={labelId}>Automatic</span>
      <span className="text-muted-foreground text-xs" id={descriptionId}>
        Syncs as soon as something changes.
      </span>
    </span>
  </label>
</RadioGroup>

Card style selection

<RadioGroup aria-label="Workspace plan" defaultValue="team">
  <Label className="flex w-full items-center justify-between gap-6 rounded-lg border p-3 hover:bg-accent/50 has-data-checked:border-primary/48 has-data-checked:bg-accent/50">
    <span className="grid gap-1">
      <span>Team</span>
      <span className="text-muted-foreground text-xs">
        Shared workspaces and roles.
      </span>
    </span>
    <Radio value="team" />
  </Label>
</RadioGroup>

Horizontal layout

<RadioGroup
  aria-label="Billing interval"
  className="flex-row flex-wrap"
  defaultValue="monthly"
>
  <Label><Radio value="monthly" />Monthly</Label>
  <Label><Radio value="yearly" />Yearly</Label>
</RadioGroup>

Form integration

Plan: not submitted
<Form onSubmit={onSubmit}>
  <Field name="plan">
    <FieldLabel>Choose a plan</FieldLabel>
    <RadioGroup aria-label="Plan" defaultValue="starter" name="plan">
      <Label><Radio value="starter" />Starter</Label>
      <Label><Radio value="pro" />Pro</Label>
    </RadioGroup>
  </Field>
  <Button loading={loading} type="submit">Submit</Button>
  <span>{status}</span>
</Form>

API Reference

The reference lists the source-owned exports and props that are easy to miss in visual examples. Inherited Base UI props remain available unless the wrapper narrows them.

  • RadioGroup accepts the Base UI group props, controlled or uncontrolled value, disabled, readOnly and size="sm" | "default" | "lg" | "xl".
  • Each Radio requires a unique value and can override disabled, readOnly and inherited size.
  • disabled on the group disables every option; use item-level disabled only when individual choices are unavailable.
  • readOnly prevents changes without applying disabled styling, so accompany it with clear context when the state might otherwise look editable.

Motion

Component motion uses the shared Siteplane motion contract. See the global motion guide for provider setup, tokens and reduced-motion behavior.

  • Selection updates the native indicator, border and focus styles without changing layout dimensions.
  • Radio Group adds no group-level enter or exit animation. System and provider reduced-motion modes move the indicator directly to its final state instead of interpolating its fade and scale.

Accessibility

  • Give every group an accessible name through aria-label or aria-labelledby.
  • Couple visible option labels to their Radio controls and give standalone controls an explicit label.
  • Pair invalid state with visible error text; disabled and read-only state must be understandable without relying on color alone.

Implementation Guidance

  • Use Checkbox Group for multiple selection and Select when the option set should remain collapsed.
  • Prefer group-level disabled when the complete choice is unavailable.
  • Do not build raw radio inputs outside the Radio Group contract.

On This Page