Inputs
Radio group
One choice from a small set, shown all at once. Use the list style for simple options and the card style when each option needs a description, an icon or a price. It's built on native radios, so the keyboard behaviour comes for free.
<div className="mx-auto w-full max-w-2xl"> <RadioGroup label="Plan" description="You can switch plans at any time." variant="card" columns={3} defaultValue="team"> <Radio value="solo" icon={<User strokeWidth={1.75} />} label="Solo" meta="$9/mo" description="One seat, unlimited invoices." /> <Radio value="team" icon={<Users strokeWidth={1.75} />} label="Team" meta="$29/mo" description="Up to 10 seats and shared clients." /> <Radio value="business" icon={<Building2 strokeWidth={1.75} />} label="Business" meta="$79/mo" description="SSO, audit log and priority support." /> </RadioGroup></div>Usage
components/ui/radio-group.tsx
import { Radio, RadioGroup } from "@/components/ui/radio-group"; export function Example() { return ( <RadioGroup label="Billing cycle" defaultValue="monthly"> <Radio value="monthly" label="Monthly" /> <Radio value="yearly" label="Yearly" description="Two months free." /> </RadioGroup> );}Examples
Cards
<div className="mx-auto w-full max-w-2xl"> <RadioGroup label="Plan" description="You can switch plans at any time." variant="card" columns={3} defaultValue="team"> <Radio value="solo" icon={<User strokeWidth={1.75} />} label="Solo" meta="$9/mo" description="One seat, unlimited invoices." /> <Radio value="team" icon={<Users strokeWidth={1.75} />} label="Team" meta="$29/mo" description="Up to 10 seats and shared clients." /> <Radio value="business" icon={<Building2 strokeWidth={1.75} />} label="Business" meta="$79/mo" description="SSO, audit log and priority support." /> </RadioGroup></div>List
<div className="mx-auto w-full max-w-sm"> <RadioGroup label="When a payment fails" defaultValue="retry"> <Radio value="retry" label="Retry automatically" description="Up to 3 times over 7 days." /> <Radio value="notify" label="Email the client" description="Send a link to update their card." /> <Radio value="pause" label="Pause the subscription" /> </RadioGroup></div>Horizontal
<div className="mx-auto w-fit"> <RadioGroup label="Invoice language" orientation="horizontal" defaultValue="en"> <Radio value="en" label="English" /> <Radio value="pt" label="Português" /> <Radio value="de" label="Deutsch" /> </RadioGroup></div>Disabled and error
<div className="mx-auto grid w-full max-w-xl gap-8 sm:grid-cols-2"> <RadioGroup label="Payout schedule" defaultValue="weekly"> <Radio value="daily" label="Daily" disabled description="Available on Business." /> <Radio value="weekly" label="Weekly" /> <Radio value="monthly" label="Monthly" /> </RadioGroup> <RadioGroup label="Tax treatment" error="Choose how VAT is applied." required> <Radio value="inclusive" label="Prices include VAT" /> <Radio value="exclusive" label="Add VAT on top" /> </RadioGroup></div>Props
RadioGroup
PropTypeDefaultDescription
labelrequiredReactNodedefault: —Legend for the group.hideLabelbooleandefault: falseKeep the legend for screen readers only.descriptionReactNodedefault: —Help text under the legend.errorReactNodedefault: —Error message; marks the group invalid.value / defaultValuestringdefault: —Controlled or initial selected value.onValueChange(value: string) => voiddefault: —Called when the selection changes.variant"list" | "card"default: "list"Plain rows or bordered cards.orientation"vertical" | "horizontal"default: "vertical"List layout direction.columns1 | 2 | 3default: 1Card columns from the sm breakpoint.namestringdefault: —Form field name. Generated when omitted.disabled / requiredbooleandefault: —Apply to the whole group.
Radio
PropTypeDefaultDescription
valuerequiredstringdefault: —Value submitted when selected.labelrequiredReactNodedefault: —Option title.descriptionReactNodedefault: —Second line.metaReactNodedefault: —Card only: right side of the title row, e.g. a price.iconReactNodedefault: —Card only: icon above the title.disabledbooleandefault: —Disable this option.
Accessibility
- Native radios share one name: Tab enters the group on the selected option, arrow keys move and select, Tab leaves the group.
- The group is a <fieldset role="radiogroup"> with a <legend>, so the question is announced with each option.
- Descriptions are linked to each radio with aria-describedby.
- In the card style the whole card is the label, so it's one large click target; the focus ring wraps the card.
- Selected cards get a thicker accent border and a filled radio, not just a tint.