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.

tsx
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.

tsx
<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.

MonthlyQuarterlyYearly
tsx
<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

tsx
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

MonthlyQuarterlyYearly

Selected: monthly

tsx
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

tsx
// 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.

tsx
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.

tsx
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.

PropTypeDefaultDescription
variant"primary" | "secondary"Visual style of the group container.
valuestringControlled selected value.
defaultValuestringInitial value for uncontrolled usage.
onValueChange(value: string) => voidCallback when selection changes.
disabledbooleanfalseDisables all items in the group.
requiredbooleanfalseMarks the group as required.
namestringName attribute for form submission.

RadioGroupItem

The convenience item component with built-in indicator.

PropTypeDefaultDescription
variant"primary" | "secondary""primary"Visual style of the radio item.
valuestringUnique value identifying this radio.
disabledbooleanfalseDisables this item.
childrenReactNodeLabel content (rendered for secondary variant).

RadioGroupRoot

The composable root primitive.

PropTypeDefaultDescription
valuestringControlled selected value.
defaultValuestringInitial value for uncontrolled usage.
onValueChange(value: string) => voidCallback when selection changes.
disabledbooleanfalseDisables all items.

RadioItem

The composable radio item primitive.

PropTypeDefaultDescription
valuestringUnique value identifying this radio.
disabledbooleanfalseDisables this item.

RadioIndicator

The composable indicator primitive.

PropTypeDefaultDescription
childrenReactNodeFilled circleCustom indicator content.
keepMountedbooleanfalseKeep in DOM when unchecked.

Accessibility

  • Radio group uses role="radiogroup" with arrow key navigation
  • Each radio is keyboard accessible with Space to select
  • Focus is visible with a ring indicator
  • Associates with labels via id and htmlFor
  • Supports aria-labelledby for group labels
  • Only one radio can be selected at a time (enforced by the group)
tsx
// 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
© 2026 Rogo Technologies Inc.