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
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.
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.
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.
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
| Name | Type | Values | Default | Description |
|---|---|---|---|---|
| orientation | enum | vertical | horizontal | vertical | Set on RadioGroup. vertical for options with descriptions or long labels; horizontal for two or three short ones. Arrow keys follow it. |
| defaultValue | string | - | - | Set on RadioGroup. The value picked on first render, when you don't control it. Pick a sensible default when one exists. |
| value | string | - | - | Set on RadioGroup. The picked value, when you control it. Pair it with onValueChange. |
| onValueChange | function | - | - | Set on RadioGroup. Called with the value of the newly picked option. |
| disabled | boolean | - | false | On RadioGroup, disables every option and dims the labels; on one RadioGroupItem, only that option. |
| required | boolean | - | false | Set on RadioGroup. An option must be picked to submit the form. |
| name | string | - | - | Set on RadioGroup. Form name; the form submits the picked value under it. |
| label | node | - | - | Set on RadioGroupItem. The visible name, after the circle. A press on it picks the option. |
| description | node | - | - | Set on RadioGroupItem. A second line under the label: what this option means. Needs label. |
Slots
| Name | Required | Description |
|---|---|---|
| items | yes | Two to five RadioGroupItem, each with a value and a label. |
Anatomy
| Part | Description |
|---|---|
| root | RadioGroup: the options in a column (gap-md), or in a row that wraps (gap-x-lg, gap-y-md). |
| item | RadioGroupItem: a 16px circle, control border on the sunken fill. Picked, the accent fills it to the edge. |
| indicator | The dot in a picked circle: 6px, in the on-accent icon color (the indicator's ::after in currentColor). |
| hitArea | Transparent ::before centered on the circle, never smaller than size-target-min (24px on mouse, 44px on touch). |
| row | With label: the row that holds the circle, then the Label and the description, gap-sm. Shared with Checkbox and Switch. |
| label | With label: a Label after the circle, level with it on its first line. |
| description | With description: 13px secondary text under the label, gap-xs. |
Accessibility
Role: radiogroup
| Keys | Action |
|---|---|
| Tab | Moves focus into the group, to the picked option (or the first one), and out of it. The group is one tab stop. |
| Arrow keys | Move to the next or previous option and pick it, wrapping from the last to the first. |
| Space | Picks 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.jsonThis 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").