import { Label } from "@/components/ui/label";
import {
RadioGroup,
RadioGroupItem,
} from "@/components/ui/radio-group";
export function RadioGroupDemo() {
return (
<RadioGroup defaultValue="comfortable" className="w-fit">
<div className="flex items-center gap-3">
<RadioGroupItem value="default" id="radio-demo-r1" />
<Label htmlFor="radio-demo-r1">Default</Label>
</div>
<div className="flex items-center gap-3">
<RadioGroupItem value="comfortable" id="radio-demo-r2" />
<Label htmlFor="radio-demo-r2">Comfortable</Label>
</div>
<div className="flex items-center gap-3">
<RadioGroupItem value="compact" id="radio-demo-r3" />
<Label htmlFor="radio-demo-r3">Compact</Label>
</div>
</RadioGroup>
);
}
pnpm dlx shadcn@latest add https://herocn.dev/r/radio-group.jsonimport { Label } from "@/components/ui/label"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"<RadioGroup defaultValue="option-one">
<div className="flex items-center gap-3">
<RadioGroupItem value="option-one" id="option-one" />
<Label htmlFor="option-one">Option One</Label>
</div>
<div className="flex items-center gap-3">
<RadioGroupItem value="option-two" id="option-two" />
<Label htmlFor="option-two">Option Two</Label>
</div>
</RadioGroup>Use the following composition to build a RadioGroup:
RadioGroup
├── RadioGroupItem
└── RadioGroupItemPair options with Label or Field (FieldLabel, FieldDescription) for accessible names and richer layouts.
Use defaultValue for an uncontrolled group, or value and onValueChange for a controlled selection.
"use client"
import * as React from "react"
import { Field, FieldLabel } from "@/components/ui/field"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
export function Example() {
const [plan, setPlan] = React.useState("monthly")
return (
<RadioGroup value={plan} onValueChange={setPlan} className="w-fit">
<Field orientation="horizontal">
<RadioGroupItem value="monthly" id="plan-monthly-ex" />
<FieldLabel htmlFor="plan-monthly-ex" className="font-normal">
Monthly
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="yearly" id="plan-yearly-ex" />
<FieldLabel htmlFor="plan-yearly-ex" className="font-normal">
Yearly
</FieldLabel>
</Field>
</RadioGroup>
)
}Use the variant prop on RadioGroup for a softer secondary style on tinted panels (see In surface).
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import {
RadioGroup,
RadioGroupItem,
} from "@/components/ui/radio-group";
export function RadioGroupVariants() {
return (
<FieldGroup className="mx-auto w-full max-w-xs gap-4">
<RadioGroup defaultValue="one" className="w-fit">
<Field orientation="horizontal">
<RadioGroupItem value="one" id="radio-var-def-1" />
<FieldLabel htmlFor="radio-var-def-1" className="font-normal">
Default
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="two" id="radio-var-def-2" />
<FieldLabel htmlFor="radio-var-def-2" className="font-normal">
Style
</FieldLabel>
</Field>
</RadioGroup>
<RadioGroup defaultValue="one" variant="secondary" className="w-fit">
<Field orientation="horizontal">
<RadioGroupItem value="one" id="radio-var-sec-1" />
<FieldLabel htmlFor="radio-var-sec-1" className="font-normal">
Secondary
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="two" id="radio-var-sec-2" />
<FieldLabel htmlFor="radio-var-sec-2" className="font-normal">
Style
</FieldLabel>
</Field>
</RadioGroup>
</FieldGroup>
);
}
Use Field, FieldContent, and FieldDescription for helper text beside each option.
Standard spacing for most use cases.
More space between elements.
Minimal spacing for dense layouts.
import {
Field,
FieldContent,
FieldDescription,
FieldLabel,
} from "@/components/ui/field";
import {
RadioGroup,
RadioGroupItem,
} from "@/components/ui/radio-group";
export function RadioGroupDescription() {
return (
<RadioGroup defaultValue="comfortable" className="w-fit">
<Field orientation="horizontal">
<RadioGroupItem value="default" id="radio-desc-r1" />
<FieldContent>
<FieldLabel htmlFor="radio-desc-r1">Default</FieldLabel>
<FieldDescription>
Standard spacing for most use cases.
</FieldDescription>
</FieldContent>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="comfortable" id="radio-desc-r2" />
<FieldContent>
<FieldLabel htmlFor="radio-desc-r2">Comfortable</FieldLabel>
<FieldDescription>More space between elements.</FieldDescription>
</FieldContent>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="compact" id="radio-desc-r3" />
<FieldContent>
<FieldLabel htmlFor="radio-desc-r3">Compact</FieldLabel>
<FieldDescription>
Minimal spacing for dense layouts.
</FieldDescription>
</FieldContent>
</Field>
</RadioGroup>
);
}
Use FieldLabel to wrap the entire Field for a clickable card-style selection.
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
FieldTitle,
} from "@/components/ui/field";
import {
RadioGroup,
RadioGroupItem,
} from "@/components/ui/radio-group";
export function FieldChoiceCard() {
return (
<FieldGroup className="w-full max-w-xs">
<FieldSet>
<FieldLegend variant="label">Compute Environment</FieldLegend>
<FieldDescription>
Select the compute environment for your cluster.
</FieldDescription>
<RadioGroup variant="secondary" defaultValue="kubernetes">
<FieldLabel htmlFor="kubernetes-r2h">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Kubernetes</FieldTitle>
<FieldDescription>
Run GPU workloads on a K8s cluster.
</FieldDescription>
</FieldContent>
<RadioGroupItem value="kubernetes" id="kubernetes-r2h" />
</Field>
</FieldLabel>
<FieldLabel htmlFor="vm-z4k">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Virtual Machine</FieldTitle>
<FieldDescription>
Access a cluster to run GPU workloads.
</FieldDescription>
</FieldContent>
<RadioGroupItem value="vm" id="vm-z4k" />
</Field>
</FieldLabel>
</RadioGroup>
</FieldSet>
</FieldGroup>
);
}
Use FieldSet and FieldLegend to group radio items with a label and description.
import {
Field,
FieldDescription,
FieldLabel,
FieldLegend,
FieldSet,
} from "@/components/ui/field";
import {
RadioGroup,
RadioGroupItem,
} from "@/components/ui/radio-group";
export function FieldRadio() {
return (
<FieldSet className="w-full max-w-xs">
<FieldLegend variant="label">Subscription Plan</FieldLegend>
<FieldDescription>
Yearly and lifetime plans offer significant savings.
</FieldDescription>
<RadioGroup defaultValue="monthly">
<Field orientation="horizontal">
<RadioGroupItem value="monthly" id="plan-monthly" />
<FieldLabel htmlFor="plan-monthly" className="font-normal">
Monthly ($9.99/month)
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="yearly" id="plan-yearly" />
<FieldLabel htmlFor="plan-yearly" className="font-normal">
Yearly ($99.99/year)
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="lifetime" id="plan-lifetime" />
<FieldLabel htmlFor="plan-lifetime" className="font-normal">
Lifetime ($299.99)
</FieldLabel>
</Field>
</RadioGroup>
</FieldSet>
);
}
Pass disabled to a RadioGroupItem. Add data-disabled on the surrounding Field when the row should use disabled field styling.
import { Field, FieldLabel } from "@/components/ui/field";
import {
RadioGroup,
RadioGroupItem,
} from "@/components/ui/radio-group";
export function RadioGroupDisabled() {
return (
<RadioGroup defaultValue="option2" className="w-fit">
<Field orientation="horizontal" data-disabled>
<RadioGroupItem value="option1" id="radio-dis-1" disabled />
<FieldLabel htmlFor="radio-dis-1" className="font-normal">
Disabled
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="option2" id="radio-dis-2" />
<FieldLabel htmlFor="radio-dis-2" className="font-normal">
Option 2
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="option3" id="radio-dis-3" />
<FieldLabel htmlFor="radio-dis-3" className="font-normal">
Option 3
</FieldLabel>
</Field>
</RadioGroup>
);
}
Set aria-invalid on RadioGroupItem and data-invalid on the Field wrapper to show invalid styles.
import {
Field,
FieldDescription,
FieldLabel,
FieldLegend,
FieldSet,
} from "@/components/ui/field";
import {
RadioGroup,
RadioGroupItem,
} from "@/components/ui/radio-group";
export function RadioGroupInvalid() {
return (
<FieldSet className="w-full max-w-xs">
<FieldLegend variant="label">Notification Preferences</FieldLegend>
<FieldDescription>
Choose how you want to receive notifications.
</FieldDescription>
<RadioGroup defaultValue="email">
<Field orientation="horizontal" data-invalid>
<RadioGroupItem value="email" id="radio-inv-email" aria-invalid />
<FieldLabel htmlFor="radio-inv-email" className="font-normal">
Email only
</FieldLabel>
</Field>
<Field orientation="horizontal" data-invalid>
<RadioGroupItem value="sms" id="radio-inv-sms" aria-invalid />
<FieldLabel htmlFor="radio-inv-sms" className="font-normal">
SMS only
</FieldLabel>
</Field>
<Field orientation="horizontal" data-invalid>
<RadioGroupItem value="both" id="radio-inv-both" aria-invalid />
<FieldLabel htmlFor="radio-inv-both" className="font-normal">
Both Email & SMS
</FieldLabel>
</Field>
</RadioGroup>
</FieldSet>
);
}
Use variant="secondary" when placing a radio group inside a Surface or card so controls match the panel background.
Standard spacing for most use cases.
More space between elements.
Minimal spacing for dense layouts.
import {
Field,
FieldContent,
FieldDescription,
FieldLabel,
} from "@/components/ui/field";
import {
RadioGroup,
RadioGroupItem,
} from "@/components/ui/radio-group";
import { Surface } from "@/components/ui/surface";
export function RadioGroupInSurface() {
return (
<Surface className="flex w-full max-w-sm flex-col gap-4 rounded-3xl p-6">
<RadioGroup
defaultValue="comfortable"
variant="secondary"
className="w-fit"
>
<Field orientation="horizontal">
<RadioGroupItem value="default" id="radio-surf-r1" />
<FieldContent>
<FieldLabel htmlFor="radio-surf-r1">Default</FieldLabel>
<FieldDescription>
Standard spacing for most use cases.
</FieldDescription>
</FieldContent>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="comfortable" id="radio-surf-r2" />
<FieldContent>
<FieldLabel htmlFor="radio-surf-r2">Comfortable</FieldLabel>
<FieldDescription>More space between elements.</FieldDescription>
</FieldContent>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="compact" id="radio-surf-r3" />
<FieldContent>
<FieldLabel htmlFor="radio-surf-r3">Compact</FieldLabel>
<FieldDescription>
Minimal spacing for dense layouts.
</FieldDescription>
</FieldContent>
</Field>
</RadioGroup>
</Surface>
);
}
Radio layout follows inline direction; use the same Field patterns as in LTR. See the RTL guide for app-wide configuration.
تباعد قياسي لمعظم حالات الاستخدام.
مساحة أكبر بين العناصر.
تباعد أدنى للتخطيطات الكثيفة.
"use client";
import {
type Translations,
useTranslation,
} from "@/components/language-selector";
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/components/ui/field";
import {
RadioGroup,
RadioGroupItem,
} from "@/components/ui/radio-group";
const translations: Translations = {
en: {
dir: "ltr",
values: {
default: "Default",
defaultDescription: "Standard spacing for most use cases.",
comfortable: "Comfortable",
comfortableDescription: "More space between elements.",
compact: "Compact",
compactDescription: "Minimal spacing for dense layouts.",
},
},
ar: {
dir: "rtl",
values: {
default: "افتراضي",
defaultDescription: "تباعد قياسي لمعظم حالات الاستخدام.",
comfortable: "مريح",
comfortableDescription: "مساحة أكبر بين العناصر.",
compact: "مضغوط",
compactDescription: "تباعد أدنى للتخطيطات الكثيفة.",
},
},
he: {
dir: "rtl",
values: {
default: "ברירת מחדל",
defaultDescription: "ריווח סטנדרטי לרוב מקרי השימוש.",
comfortable: "נוח",
comfortableDescription: "יותר מקום בין האלמנטים.",
compact: "קומפקטי",
compactDescription: "ריווח מינימלי לפריסות צפופות.",
},
},
};
export function RadioGroupRtl() {
const { language, dir, t } = useTranslation(translations, "ar");
return (
<FieldGroup lang={language} dir={dir} className="w-full">
<RadioGroup defaultValue="comfortable" className="w-fit">
<Field orientation="horizontal">
<RadioGroupItem value="default" id="radio-rtl-r1" />
<FieldContent>
<FieldLabel htmlFor="radio-rtl-r1">{t.default}</FieldLabel>
<FieldDescription>{t.defaultDescription}</FieldDescription>
</FieldContent>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="comfortable" id="radio-rtl-r2" />
<FieldContent>
<FieldLabel htmlFor="radio-rtl-r2">{t.comfortable}</FieldLabel>
<FieldDescription>{t.comfortableDescription}</FieldDescription>
</FieldContent>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="compact" id="radio-rtl-r3" />
<FieldContent>
<FieldLabel htmlFor="radio-rtl-r3">{t.compact}</FieldLabel>
<FieldDescription>{t.compactDescription}</FieldDescription>
</FieldContent>
</Field>
</RadioGroup>
</FieldGroup>
);
}
The RadioGroup component forwards props to @base-ui/react RadioGroup and supports the following additional prop:
| Prop | Type | Default |
|---|---|---|
| variant | "default" | "secondary" | "default" |
RadioGroupItem forwards props to the Base UI Radio root. For the full prop surface, see the Base UI Radio Group API.