Radio Group
Radio groups let users select a single option from a set of mutually exclusive choices.
Overview
The RadioGroup component provides a set of radio buttons where only one can be selected at a time. Built on Base UI Radio, it supports keyboard navigation, accessibility, and two visual variants.
import { RadioGroup, RadioGroupItem } from "@rogo-technologies/ui/radio-group"
<RadioGroup defaultValue="option-one">
<div className="flex items-center gap-2">
<RadioGroupItem value="option-one" id="option-one" />
<label htmlFor="option-one">Option One</label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="option-two" id="option-two" />
<label htmlFor="option-two">Option Two</label>
</div>
</RadioGroup>Basic
A simple radio group with labels. Each RadioGroupItem needs a unique value prop.
<RadioGroup defaultValue="comfortable">
<div className="flex items-center gap-2">
<RadioGroupItem value="default" id="r1" />
<label htmlFor="r1">Default</label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="comfortable" id="r2" />
<label htmlFor="r2">Comfortable</label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="compact" id="r3" />
<label htmlFor="r3">Compact</label>
</div>
</RadioGroup>Secondary Variant
Use the secondary variant for a pill/tab toggle style. Pass variant="secondary" to both RadioGroup and RadioGroupItem. Children of secondary items are rendered as the label.
<RadioGroup variant="secondary" defaultValue="monthly">
<RadioGroupItem variant="secondary" value="monthly">Monthly</RadioGroupItem>
<RadioGroupItem variant="secondary" value="quarterly">Quarterly</RadioGroupItem>
<RadioGroupItem variant="secondary" value="yearly">Yearly</RadioGroupItem>
</RadioGroup>Controlled
Use value and onValueChange for controlled radio group state.
Selected: comfortable
const [value, setValue] = useState("comfortable")
<RadioGroup value={value} onValueChange={setValue}>
<div className="flex items-center gap-2">
<RadioGroupItem value="default" id="option-default" />
<label htmlFor="option-default">Default</label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="comfortable" id="option-comfortable" />
<label htmlFor="option-comfortable">Comfortable</label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="compact" id="option-compact" />
<label htmlFor="option-compact">Compact</label>
</div>
</RadioGroup>Controlled Secondary
Selected: monthly
const [period, setPeriod] = useState("monthly")
<RadioGroup variant="secondary" value={period} onValueChange={setPeriod}>
<RadioGroupItem variant="secondary" value="monthly">Monthly</RadioGroupItem>
<RadioGroupItem variant="secondary" value="quarterly">Quarterly</RadioGroupItem>
<RadioGroupItem variant="secondary" value="yearly">Yearly</RadioGroupItem>
</RadioGroup>Disabled
Use the disabled prop on the group or individual items to prevent interaction.
Entire group disabled
Single item disabled
// Disable the entire group
<RadioGroup defaultValue="option-a" disabled>
<RadioGroupItem value="option-a" />
<RadioGroupItem value="option-b" />
</RadioGroup>
// Disable a single item
<RadioGroup defaultValue="available">
<RadioGroupItem value="available" />
<RadioGroupItem value="unavailable" disabled />
</RadioGroup>Composable API
Use RadioGroupRoot, RadioItem, and RadioIndicator for custom radio implementations with full control over the indicator content.
import { RadioGroupRoot, RadioItem, RadioIndicator } from "@rogo-technologies/ui/radio-group"
// Default indicator (filled circle)
<RadioGroupRoot defaultValue="option-a">
<RadioItem value="option-a">
<RadioIndicator />
</RadioItem>
</RadioGroupRoot>
// Custom indicator content
<RadioGroupRoot defaultValue="option-a">
<RadioItem value="option-a">
<RadioIndicator>
<span className="text-brand text-[10px]">●</span>
</RadioIndicator>
</RadioItem>
</RadioGroupRoot>With Form Libraries
The radio group works with form libraries like React Hook Form.
import { useForm, Controller } from "react-hook-form"
const { control, handleSubmit } = useForm({
defaultValues: { plan: "free" }
})
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="plan"
render={({ field }) => (
<RadioGroup value={field.value} onValueChange={field.onChange}>
<RadioGroupItem value="free" id="plan-free" />
<label htmlFor="plan-free">Free</label>
<RadioGroupItem value="pro" id="plan-pro" />
<label htmlFor="plan-pro">Pro</label>
</RadioGroup>
)}
/>
</form>Props
RadioGroup
The convenience root component with variant support.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "primary" | "secondary" | — | Visual style of the group container. |
value | string | — | Controlled selected value. |
defaultValue | string | — | Initial value for uncontrolled usage. |
onValueChange | (value: string) => void | — | Callback when selection changes. |
disabled | boolean | false | Disables all items in the group. |
required | boolean | false | Marks the group as required. |
name | string | — | Name attribute for form submission. |
RadioGroupItem
The convenience item component with built-in indicator.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "primary" | "secondary" | "primary" | Visual style of the radio item. |
value | string | — | Unique value identifying this radio. |
disabled | boolean | false | Disables this item. |
children | ReactNode | — | Label content (rendered for secondary variant). |
RadioGroupRoot
The composable root primitive.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Controlled selected value. |
defaultValue | string | — | Initial value for uncontrolled usage. |
onValueChange | (value: string) => void | — | Callback when selection changes. |
disabled | boolean | false | Disables all items. |
RadioItem
The composable radio item primitive.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Unique value identifying this radio. |
disabled | boolean | false | Disables this item. |
RadioIndicator
The composable indicator primitive.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | Filled circle | Custom indicator content. |
keepMounted | boolean | false | Keep in DOM when unchecked. |
Accessibility
- Radio group uses
role="radiogroup"with arrow key navigation - Each radio is keyboard accessible with
Spaceto select - Focus is visible with a ring indicator
- Associates with labels via
idandhtmlFor - Supports
aria-labelledbyfor group labels - Only one radio can be selected at a time (enforced by the group)
// Proper labeling
<RadioGroup aria-labelledby="plan-heading">
<h3 id="plan-heading">Choose a plan</h3>
<RadioGroupItem value="free" id="plan-free" />
<label htmlFor="plan-free">Free</label>
</RadioGroup>Usage Guidelines
Do
- Always pair radio items with visible labels
- Use radio groups for mutually exclusive options (only one can be selected)
- Provide a sensible default selection when possible
- Group related options together visually
- Use the secondary variant for compact inline toggles (e.g. billing period)
Don't
- Don't use radio buttons when multiple selections are allowed — use checkboxes instead
- Don't use radio groups for binary on/off toggles — use a switch instead
- Don't have more than 7 options in a radio group — consider a select/combobox instead
- Don't mix primary and secondary variants in the same group
- Don't leave a required radio group without a default — users can't deselect