Skip to content
Components

RadioGroup

One choice out of a few options the user compares side by side. Each option is a 16px circle with its label after it and an optional description under the label; the picked one fills with the accent and shows an on-accent dot.

Preview

Plan

Up to 3 projects.

Unlimited projects and version history.

Pro, plus shared libraries and roles.

Props

  • orientation
  • defaultValue
  • value
  • disabled
  • descriptions
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"

<div className="flex flex-col gap-sm">
  <span id="preview-plan-heading" className="text-sm font-medium text-text-primary">Plan</span>
  <RadioGroup aria-labelledby="preview-plan-heading" defaultValue="pro">
    <RadioGroupItem value="free" label="Free" description="Up to 3 projects." />
    <RadioGroupItem value="pro" label="Pro" description="Unlimited projects and version history." />
    <RadioGroupItem value="team" label="Team" description="Pro, plus shared libraries and roles." />
  </RadioGroup>
</div>

Usage

When to use

  • Pick exactly one of two to five options the user should see and compare at once, such as a plan.
  • Options that need a second line to say what they mean.

When not to use

  • More than five options, or options the user already knows, such as a country. Use Select.
  • Turning one setting on or off. Use Switch.
  • Picking several options. Use Checkbox.

Variants

orientation

Set on RadioGroup. vertical for options with descriptions or long labels; horizontal for two or three short ones. Arrow keys follow it.

  • Plan

    Up to 3 projects.

    Unlimited projects and version history.

    Pro, plus shared libraries and roles.

    verticaldefault

  • Plan

    Up to 3 projects.

    Unlimited projects and version history.

    Pro, plus shared libraries and roles.

    horizontal

import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"

<div className="flex flex-col gap-sm">
  <span id="preview-plan-heading-orientation-vertical" className="text-sm font-medium text-text-primary">Plan</span>
  <RadioGroup aria-labelledby="preview-plan-heading-orientation-vertical" defaultValue="pro">
    <RadioGroupItem value="free" label="Free" description="Up to 3 projects." />
    <RadioGroupItem value="pro" label="Pro" description="Unlimited projects and version history." />
    <RadioGroupItem value="team" label="Team" description="Pro, plus shared libraries and roles." />
  </RadioGroup>
</div>
<div className="flex flex-col gap-sm">
  <span id="preview-plan-heading-orientation-horizontal" className="text-sm font-medium text-text-primary">Plan</span>
  <RadioGroup aria-labelledby="preview-plan-heading-orientation-horizontal" defaultValue="pro" orientation="horizontal">
    <RadioGroupItem value="free" label="Free" description="Up to 3 projects." />
    <RadioGroupItem value="pro" label="Pro" description="Unlimited projects and version history." />
    <RadioGroupItem value="team" label="Team" description="Pro, plus shared libraries and roles." />
  </RadioGroup>
</div>

Examples

Default

A visible heading names the group through aria-labelledby.

Plan

Up to 3 projects.

Unlimited projects and version history.

Pro, plus shared libraries and roles.

import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"

<div className="flex flex-col gap-sm">
  <span id="plan-heading" className="text-sm font-medium text-text-primary">Plan</span>
  <RadioGroup aria-labelledby="plan-heading" defaultValue="pro">
    <RadioGroupItem value="free" label="Free" description="Up to 3 projects." />
    <RadioGroupItem value="pro" label="Pro" description="Unlimited projects and version history." />
    <RadioGroupItem value="team" label="Team" description="Pro, plus shared libraries and roles." />
  </RadioGroup>
</div>

Short options in a row

import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"

<RadioGroup aria-label="Theme" orientation="horizontal" defaultValue="system">
  <RadioGroupItem value="system" label="System" />
  <RadioGroupItem value="light" label="Light" />
  <RadioGroupItem value="dark" label="Dark" />
</RadioGroup>

A disabled option

The arrow keys skip it.

Coming later this year.

import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"

<RadioGroup aria-label="Region" defaultValue="eu">
  <RadioGroupItem value="eu" label="Europe" />
  <RadioGroupItem value="us" label="United States" />
  <RadioGroupItem value="ap" label="Asia Pacific" description="Coming later this year." disabled />
</RadioGroup>

Do and don't

Do

  • Use it for two to five options the user should see and compare at once.
  • Put a visible heading over the group and point aria-labelledby at it.
  • Pick a default when one option is the usual choice.
  • Add a description when the label alone doesn't say what the option means.

Don't

  • Don't use it for more than five options; use Select.
  • Don't use it to turn one setting on or off; use Switch.
  • Don't use it to pick several options; use Checkbox.

Props

NameTypeValuesDefaultDescription
orientationenumvertical | horizontalverticalSet on RadioGroup. vertical for options with descriptions or long labels; horizontal for two or three short ones. Arrow keys follow it.
defaultValuestring--Set on RadioGroup. The value picked on first render, when you don't control it. Pick a sensible default when one exists.
valuestring--Set on RadioGroup. The picked value, when you control it. Pair it with onValueChange.
onValueChangefunction--Set on RadioGroup. Called with the value of the newly picked option.
disabledboolean-falseOn RadioGroup, disables every option and dims the labels; on one RadioGroupItem, only that option.
requiredboolean-falseSet on RadioGroup. An option must be picked to submit the form.
namestring--Set on RadioGroup. Form name; the form submits the picked value under it.
labelnode--Set on RadioGroupItem. The visible name, after the circle. A press on it picks the option.
descriptionnode--Set on RadioGroupItem. A second line under the label: what this option means. Needs label.

Slots

NameRequiredDescription
itemsyesTwo to five RadioGroupItem, each with a value and a label.

Anatomy

PartDescription
rootRadioGroup: the options in a column (gap-md), or in a row that wraps (gap-x-lg, gap-y-md).
itemRadioGroupItem: a 16px circle, control border on the sunken fill. Picked, the accent fills it to the edge.
indicatorThe dot in a picked circle: 6px, in the on-accent icon color (the indicator's ::after in currentColor).
hitAreaTransparent ::before centered on the circle, never smaller than size-target-min (24px on mouse, 44px on touch).
rowWith label: the row that holds the circle, then the Label and the description, gap-sm. Shared with Checkbox and Switch.
labelWith label: a Label after the circle, level with it on its first line.
descriptionWith description: 13px secondary text under the label, gap-xs.

Accessibility

Role: radiogroup

KeysAction
TabMoves focus into the group, to the picked option (or the first one), and out of it. The group is one tab stop.
Arrow keysMove to the next or previous option and pick it, wrapping from the last to the first.
SpacePicks the focused option when none is picked.
  • Name the group: aria-labelledby with the id of the visible heading over it, or aria-label. The type requires one of them.
  • Name every option with the label prop; a press on the label picks it. description is linked with aria-describedby and read after the name.
  • A disabled group dims every label; a disabled option is skipped by the arrow keys.
  • Inside a form, a hidden input carries name and the picked value.
  • The picked option differs from the others by its dot as well as its fill, so the choice never relies on color alone.

Install

npx shadcn add https://werewolf-ui.vercel.app/r/radio-group.json

This also adds styles/werewolf.css, the tokens every component uses, and any other component it needs. Import that file once in your root CSS, after @import "tailwindcss", with a path relative to that CSS file (from app/globals.css it is @import "../styles/werewolf.css").