---
id: "packages/active-ui/radio-group"
title: "RadioGroup"
description: "One choice out of a few options that are all visible, as plain items or as choice cards."
url: "/packages/active-ui/radio-group"
locale: "en"
revision: "cd3c80c00315ee0f"
---

# RadioGroup

A radio group asks for exactly one choice and shows every option at once, so the reader can compare
them. Use it for up to about six options; for more, or when the options need no comparison, a
[select](/packages/active-ui/select) takes less room. A group without a choice yet has no selected
item; once chosen, the reader cannot clear it, only change it.

**Example: Plain items and choice cards**

Client: Runs entirely in your browser. Source: `examples/ui/radio-group.tsx`.

```tsx
// apps/docs/examples/ui/radio-group.tsx
'use client';

import {useState} from 'react';

import {
  Field,
  FieldContent,
  FieldDescription,
  FieldLabel,
  FieldLegend,
  FieldSet,
  FieldTitle,
} from '@workspace/active-ui/components/primitives/field';
import {
  RadioGroup,
  RadioGroupItem,
} from '@workspace/active-ui/components/primitives/radio-group';

const PLANS = [
  {
    value: 'monthly',
    title: 'Monthly',
    description: 'Contributions are collected on the first working day.',
  },
  {
    value: 'quarterly',
    title: 'Quarterly',
    description:
      'Four collections a year, in January, April, July and October.',
  },
  {
    value: 'yearly',
    title: 'Yearly',
    description: 'One collection in January.',
  },
];

/**
 * One choice out of a few, all visible: plain items beside their labels, and
 * choice cards with a description. The group is a `FieldSet` named by its
 * legend; the arrow keys move between the items.
 */
export default function RadioGroupExample() {
  const [plan, setPlan] = useState('monthly');
  return (
    <div className="grid w-full max-w-md gap-8">
      <FieldSet>
        <FieldLegend variant="label">Statement language</FieldLegend>
        <RadioGroup defaultValue="en" aria-label="Statement language">
          {[
            ['en', 'English'],
            ['nl', 'Dutch'],
            ['de', 'German'],
          ].map(([value, label]) => (
            <Field key={value} orientation="horizontal">
              <RadioGroupItem value={value} id={`language-${value}`} />
              <FieldLabel htmlFor={`language-${value}`}>{label}</FieldLabel>
            </Field>
          ))}
        </RadioGroup>
      </FieldSet>

      <FieldSet>
        <FieldLegend variant="label">Collection</FieldLegend>
        <RadioGroup
          value={plan}
          onValueChange={(value) => setPlan(value as string)}
          aria-label="Collection"
        >
          {PLANS.map((option) => (
            <FieldLabel key={option.value} htmlFor={`plan-${option.value}`}>
              <Field orientation="horizontal">
                <RadioGroupItem
                  value={option.value}
                  id={`plan-${option.value}`}
                />
                <FieldContent>
                  <FieldTitle>{option.title}</FieldTitle>
                  <FieldDescription>{option.description}</FieldDescription>
                </FieldContent>
              </Field>
            </FieldLabel>
          ))}
        </RadioGroup>
        <p className="text-muted-foreground text-sm">Chosen: {plan}</p>
      </FieldSet>
    </div>
  );
}
```

## Usage

```tsx
import {
  Field,
  FieldLabel,
} from '@workspace/active-ui/components/primitives/field';
import {
  RadioGroup,
  RadioGroupItem,
} from '@workspace/active-ui/components/primitives/radio-group';

<RadioGroup defaultValue="monthly" aria-label="Collection">
  <Field orientation="horizontal">
    <RadioGroupItem value="monthly" id="monthly" />
    <FieldLabel htmlFor="monthly">Monthly</FieldLabel>
  </Field>
  <Field orientation="horizontal">
    <RadioGroupItem value="yearly" id="yearly" />
    <FieldLabel htmlFor="yearly">Yearly</FieldLabel>
  </Field>
</RadioGroup>;
```

## Anatomy

- `RadioGroup`: the value and the keyboard. `value` and `onValueChange` (controlled) or
  `defaultValue`; `name` for a native form. It lays out its children in a grid with a gap.
- `RadioGroupItem`: one option, by its `value`. The circle fills when it is chosen.
- Labels and descriptions come from [Field](/packages/active-ui/field): a `FieldLabel` beside
  each item, or a whole choice card, as in the example.

## What your application provides

- A name for the group: a `FieldSet` with a `FieldLegend` around it, plus `aria-label` or
  `aria-labelledby` on the group itself.
- A label for every item.
- The value. `onValueChange` reports the `value` of the chosen item, untyped unless you give the
  group a type: narrow it to the values you offer.

## Accessibility and keyboard

- The group is one tab stop: `Tab` enters it on the chosen item (or the first), the arrow keys move
  to and choose the next or previous item, and `Tab` leaves it.
- Each item is a `role="radio"` button with `aria-checked`.
- A disabled item (`disabled` on the item) is skipped; `disabled` on the group disables them all.

## API

### RadioGroup

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `defaultValue` | `any` | no | The uncontrolled value of the radio button that should be initially selected.  To render a controlled radio group, use the `value` prop instead. |
| `disabled` | `boolean` | no | Whether the component should ignore user interaction. |
| `form` | `string` | no | Identifies the form that owns the radio inputs. Useful when the radio group is rendered outside the form. |
| `inputRef` | `Ref<HTMLInputElement>` | no | A ref to access the hidden input element. |
| `name` | `string` | no | Identifies the field when a form is submitted. |
| `onValueChange` | `((value: any, eventDetails: RadioGroup.ChangeEventDetails) => void)` | no | Callback fired when the value changes. |
| `readOnly` | `boolean` | no | Whether the user should be unable to select a different radio button in the group. |
| `render` | `ComponentRenderFn<HTMLProps, RadioGroupState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |
| `required` | `boolean` | no | Whether the user must choose a value before submitting a form. |
| `value` | `any` | no | The controlled value of the radio item that should be currently selected.  To render an uncontrolled radio group, use the `defaultValue` prop instead. |

### RadioGroupItem

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `value` | `any` | yes | The unique identifying value of the radio in a group. |
| `disabled` | `boolean` | no | Whether the component should ignore user interaction. |
| `inputRef` | `Ref<HTMLInputElement>` | no | A ref to access the hidden input element. |
| `nativeButton` | `boolean` | no | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| `readOnly` | `boolean` | no | Whether the user should be unable to select the radio button. |
| `render` | `ComponentRenderFn<HTMLProps, RadioRootState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |
| `required` | `boolean` | no | Whether the user must choose a value before submitting a form. |

> [!NOTE]
> **Based on shadcn/ui**
>
> This page adapts the Radio Group documentation of shadcn/ui (MIT licence) to
> this package; see [Third-party notices](/third-party-notices).
