Radio Group
Single-selection group. Use value + onValueChange for controlled behaviour.
Examples
Choice Card
Use FieldLabel to wrap the entire Field for a clickable card-style selection.
import { Field, FieldContent, FieldDescription, FieldLabel, FieldTitle } from "@codefast/ui/field";
import { RadioGroup, RadioGroupItem } from "@codefast/ui/radio-group";
export function RadioGroupChoiceCard() {
return (
<RadioGroup defaultValue="plus" className="max-w-sm">
<FieldLabel htmlFor="plus-plan">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Plus</FieldTitle>
<FieldDescription>For individuals and small teams.</FieldDescription>
</FieldContent>
<RadioGroupItem value="plus" id="plus-plan" />
</Field>
</FieldLabel>
<FieldLabel htmlFor="pro-plan">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Pro</FieldTitle>
<FieldDescription>For growing businesses.</FieldDescription>
</FieldContent>
<RadioGroupItem value="pro" id="pro-plan" />
</Field>
</FieldLabel>
<FieldLabel htmlFor="enterprise-plan">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Enterprise</FieldTitle>
<FieldDescription>For large teams and enterprises.</FieldDescription>
</FieldContent>
<RadioGroupItem value="enterprise" id="enterprise-plan" />
</Field>
</FieldLabel>
</RadioGroup>
);
}
Description
Radio group items with a description using the Field component.
Standard spacing for most use cases.
More space between elements.
Minimal spacing for dense layouts.
import { Field, FieldContent, FieldDescription, FieldLabel } from "@codefast/ui/field";
import { RadioGroup, RadioGroupItem } from "@codefast/ui/radio-group";
export function RadioGroupDescription() {
return (
<RadioGroup defaultValue="comfortable" className="w-fit">
<Field orientation="horizontal">
<RadioGroupItem value="default" id="desc-r1" />
<FieldContent>
<FieldLabel htmlFor="desc-r1">Default</FieldLabel>
<FieldDescription>Standard spacing for most use cases.</FieldDescription>
</FieldContent>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="comfortable" id="desc-r2" />
<FieldContent>
<FieldLabel htmlFor="desc-r2">Comfortable</FieldLabel>
<FieldDescription>More space between elements.</FieldDescription>
</FieldContent>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="compact" id="desc-r3" />
<FieldContent>
<FieldLabel htmlFor="desc-r3">Compact</FieldLabel>
<FieldDescription>Minimal spacing for dense layouts.</FieldDescription>
</FieldContent>
</Field>
</RadioGroup>
);
}
Disabled
Use the disabled prop on RadioGroupItem to disable individual items.
import { Field, FieldLabel } from "@codefast/ui/field";
import { RadioGroup, RadioGroupItem } from "@codefast/ui/radio-group";
export function RadioGroupDisabled() {
return (
<RadioGroup defaultValue="option2" className="w-fit">
<Field orientation="horizontal" data-disabled>
<RadioGroupItem value="option1" id="disabled-1" disabled />
<FieldLabel htmlFor="disabled-1" className="font-normal">
Disabled
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="option2" id="disabled-2" />
<FieldLabel htmlFor="disabled-2" className="font-normal">
Option 2
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="option3" id="disabled-3" />
<FieldLabel htmlFor="disabled-3" className="font-normal">
Option 3
</FieldLabel>
</Field>
</RadioGroup>
);
}
Fieldset
Use FieldSet and FieldLegend to group radio items with a label and description.
import { Field, FieldDescription, FieldLabel, FieldLegend, FieldSet } from "@codefast/ui/field";
import { RadioGroup, RadioGroupItem } from "@codefast/ui/radio-group";
export function RadioGroupFieldset() {
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>
);
}
Invalid
Use aria-invalid on RadioGroupItem and data-invalid on Field to show validation errors.
import { Field, FieldDescription, FieldLabel, FieldLegend, FieldSet } from "@codefast/ui/field";
import { RadioGroup, RadioGroupItem } from "@codefast/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="invalid-email" aria-invalid />
<FieldLabel htmlFor="invalid-email" className="font-normal">
Email only
</FieldLabel>
</Field>
<Field orientation="horizontal" data-invalid>
<RadioGroupItem value="sms" id="invalid-sms" aria-invalid />
<FieldLabel htmlFor="invalid-sms" className="font-normal">
SMS only
</FieldLabel>
</Field>
<Field orientation="horizontal" data-invalid>
<RadioGroupItem value="both" id="invalid-both" aria-invalid />
<FieldLabel htmlFor="invalid-both" className="font-normal">
Both Email & SMS
</FieldLabel>
</Field>
</RadioGroup>
</FieldSet>
);
}
RTL
Right-to-left layout support for languages such as Arabic and Hebrew.
Translations are AI-generated for demonstration and may be imperfect.
تباعد قياسي لمعظم حالات الاستخدام.
مساحة أكبر بين العناصر.
تباعد أدنى للتخطيطات الكثيفة.
import { Field, FieldContent, FieldDescription, FieldLabel } from "@codefast/ui/field";
import { RadioGroup, RadioGroupItem } from "@codefast/ui/radio-group";
import type { Translations } from "#/features/components-catalog/components/detail/language";
import { useTranslation } from "#/features/components-catalog/components/detail/language-context";
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 { dir, t } = useTranslation(translations, "ar");
return (
<RadioGroup defaultValue="comfortable" className="w-fit" dir={dir}>
<Field orientation="horizontal">
<RadioGroupItem value="default" id="r1-rtl" dir={dir} />
<FieldContent>
<FieldLabel htmlFor="r1-rtl" dir={dir}>
{t.default}
</FieldLabel>
<FieldDescription dir={dir}>{t.defaultDescription}</FieldDescription>
</FieldContent>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="comfortable" id="r2-rtl" dir={dir} />
<FieldContent>
<FieldLabel htmlFor="r2-rtl" dir={dir}>
{t.comfortable}
</FieldLabel>
<FieldDescription dir={dir}>{t.comfortableDescription}</FieldDescription>
</FieldContent>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="compact" id="r3-rtl" dir={dir} />
<FieldContent>
<FieldLabel htmlFor="r3-rtl" dir={dir}>
{t.compact}
</FieldLabel>
<FieldDescription dir={dir}>{t.compactDescription}</FieldDescription>
</FieldContent>
</Field>
</RadioGroup>
);
}
Usage
The minimal import and composition — see Examples below for styled, real-world variants.
import { Label } from "@codefast/ui/label";
import { RadioGroup, RadioGroupItem } from "@codefast/ui/radio-group";
export function RadioGroupUsage() {
return (
<RadioGroup defaultValue="comfortable">
<div className="flex items-center gap-2">
<RadioGroupItem id="density-default" value="default" />
<Label htmlFor="density-default">Default</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem id="density-comfortable" value="comfortable" />
<Label htmlFor="density-comfortable">Comfortable</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem id="density-compact" value="compact" />
<Label htmlFor="density-compact">Compact</Label>
</div>
</RadioGroup>
);
}
Anatomy
How the parts nest — every slot the component exposes, in composition order.
Features
- Roving-tabindex ARIA radiogroup — Tab enters the group once, then Arrow keys move focus and select together.
- Composes with Field/FieldLabel/FieldDescription and FieldSet/FieldLegend for labelled items and a grouped legend, or a FieldLabel wrapping the whole Field for a clickable choice-card.
API reference
Props for each part of the component. All native element props are also forwarded.
RadioGroup
A single-selection group of radio items.
valuestringThe controlled selected value.
defaultValuestringThe selected value when initially rendered (uncontrolled).
onValueChange(value: string) => voidCalled when the selected value changes.
disabledbooleanDisables the whole group.
Default
false
RadioGroupItem
valuestringIdentifier selected when this item is chosen (required).
Accessibility
Built to be keyboard-navigable and screen-reader friendly out of the box.
| Key | Function |
|---|---|
| Tab | Moves focus into the group. |
| Arrow+Down | Selects the next item. |
| Arrow+Up | Selects the previous item. |
- Implements the ARIA radiogroup pattern with roving focus.
- Pair each RadioGroupItem with a Label via matching id / htmlFor.
- Arrow keys both move focus and change selection — that’s expected for radios.
Guidelines
Conventions that keep usage consistent across an app.
Do
- Use for 2–7 mutually-exclusive options shown at once.
- Pre-select a sensible default.
Don’t
- Don’t use radios for multi-select — use Checkbox Group.
- Don’t use a radio group when a Select saves space and the list is long.
Explore further
Ready to integrate?
Follow the Getting Started guide to install @codefast/ui, or browse the full component gallery.