# Radio Group

An accessible React radio group for Next.js forms, where the selected dot travels to the new choice, built on Radix Radio Group.

Source: https://ui.fewtone.com/components/radio-group

## Installation

```bash
npx shadcn@latest add https://ui.fewtone.com/r/radio-group.json
```

## Usage

```tsx
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"

export function Density() {
  return (
    <RadioGroup defaultValue="comfortable" aria-label="Density">
      <div>
        <RadioGroupItem id="comfortable" value="comfortable" />
        <label htmlFor="comfortable">Comfortable</label>
      </div>
      <div>
        <RadioGroupItem id="compact" value="compact" />
        <label htmlFor="compact">Compact</label>
      </div>
    </RadioGroup>
  )
}
```

## API reference

### RadioGroup

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | – | The chosen value, when you control it. |
| `defaultValue` | `string` | – | The value chosen at first. |
| `onValueChange` | `(value: string) => void` | – | Called when the user picks another option. |
| `travel` | `"slide" \| "stretch"` | `"slide"` | How the dot travels to the new choice: straight across, or stretching on the way. |
| `size` | `"sm" \| "md"` | `"md"` | 16 or 20px radios, the sizes of Checkbox and Switch. |
| `orientation` | `"horizontal" \| "vertical"` | – | Which arrow keys move the choice. Both work when it's left out. |
| `disabled` | `boolean` | `false` | Prevents interaction with every option. |
| `required` | `boolean` | `false` | A choice is needed to submit the form. |
| `name` | `string` | – | Submits the chosen value with a form. |

### RadioGroupItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | – | The value this radio picks. |
| `disabled` | `boolean` | `false` | Prevents picking this option. |

**When to use it:** Use RadioGroup for one choice from a few options that should all stay visible, in forms and settings. Use Select for long lists, Switcher for a compact segmented control, and Checkbox when several can be picked.
**Built on:** Radix Radio Group.
**Accessibility pattern:** https://www.w3.org/WAI/ARIA/apg/patterns/radio/
**npm packages (installed by the CLI):** class-variance-authority@^0.7.1, motion@^14.0.0, radix-ui@^1.7.0.

## Examples

### Slide and stretch

Pick another option and the dot travels to it: straight across, or stretching on the way and landing round.

```tsx
"use client";

import {
  RadioGroup,
  RadioGroupItem,
  type RadioGroupTravel,
} from "@/components/ui/radio-group";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId, useState } from "react";

type Size = "sm" | "md";

const travels: { travel: RadioGroupTravel; label: string }[] = [
  { travel: "slide", label: "Slide" },
  { travel: "stretch", label: "Stretch" },
];
const options = ["Default", "Comfortable", "Compact"];

/*
 * Both ways of travelling side by side, named underneath, with the size in the top-right corner. An empty
 * row at the bottom, as tall as the setting's row, keeps them centred.
 */
export default function RadioGroupTravelDemo() {
  const id = useId();
  const [size, setSize] = useState<Size>("md");

  return (
    <div className="flex w-full flex-col gap-6 self-stretch">
      <div className="-mt-6 -mr-6 flex items-center justify-end gap-2">
        <label htmlFor={`${id}-size`} className="text-sm text-muted-foreground">
          Size
        </label>
        <Select value={size} onValueChange={(value) => setSize(value as Size)}>
          <SelectTrigger id={`${id}-size`} size="sm" className="w-28">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="sm">Small</SelectItem>
            <SelectItem value="md">Medium</SelectItem>
          </SelectContent>
        </Select>
      </div>

      <ul className="flex flex-1 flex-wrap items-center justify-center gap-x-16 gap-y-8">
        {travels.map(({ travel, label }) => (
          <li key={travel} className="flex flex-col items-center gap-4">
            <RadioGroup
              defaultValue="comfortable"
              travel={travel}
              size={size}
              aria-label={`Density, ${label.toLowerCase()}`}
            >
              {options.map((option) => (
                <div key={option} className="flex items-center gap-3">
                  <RadioGroupItem id={`${id}-${travel}-${option}`} value={option.toLowerCase()} />
                  <label htmlFor={`${id}-${travel}-${option}`} className="text-sm">
                    {option}
                  </label>
                </div>
              ))}
            </RadioGroup>
            <span className="text-xs text-muted-foreground">{label}</span>
          </li>
        ))}
      </ul>

      <div aria-hidden className="-mb-6 h-(--size-control-sm)" />
    </div>
  );
}
```

### In a row

Left and right arrow keys move along the row, and the dot follows.

```tsx
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { useId } from "react";

export default function RadioGroupHorizontal() {
  const id = useId();
  return (
    <RadioGroup defaultValue="m" orientation="horizontal" aria-label="Size" className="flex flex-wrap gap-6">
      {["S", "M", "L", "XL"].map((option) => (
        <div key={option} className="flex items-center gap-2">
          <RadioGroupItem id={`${id}-${option}`} value={option.toLowerCase()} />
          <label htmlFor={`${id}-${option}`} className="text-sm">
            {option}
          </label>
        </div>
      ))}
    </RadioGroup>
  );
}
```

### Cards

Each card is the radio's label, so the whole card picks it. Its border follows the choice.

```tsx
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { useId } from "react";

const plans = [
  { value: "starter", title: "Starter", price: "Free", note: "For trying things out." },
  { value: "pro", title: "Pro", price: "$12 a month", note: "For one person shipping." },
  { value: "team", title: "Team", price: "$40 a month", note: "For a small team." },
];

/** Each card is the radio's label, so a click anywhere on it picks the plan; the border follows the choice */
export default function RadioGroupCards() {
  const id = useId();
  return (
    <RadioGroup defaultValue="pro" aria-label="Plan" className="w-full max-w-2xl gap-3 sm:grid-cols-3">
      {plans.map((plan) => (
        <label
          key={plan.value}
          htmlFor={`${id}-${plan.value}`}
          className="grid cursor-pointer gap-1 rounded-lg border border-input bg-background p-4 transition-colors duration-(--duration-fast) ease-out has-data-[state=checked]:border-primary"
        >
          <span className="flex items-center justify-between gap-2">
            <span className="text-sm font-medium">{plan.title}</span>
            <RadioGroupItem id={`${id}-${plan.value}`} value={plan.value} />
          </span>
          <span className="text-sm">{plan.price}</span>
          <span className="text-xs text-muted-foreground">{plan.note}</span>
        </label>
      ))}
    </RadioGroup>
  );
}
```

### Disabled

One option can't be picked; arrow keys skip it.

```tsx
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { useId } from "react";

const options = [
  { value: "email", label: "Email" },
  { value: "sms", label: "Text message", disabled: true, hint: "(not available in your region)" },
  { value: "app", label: "Authenticator app" },
];

export default function RadioGroupDisabled() {
  const id = useId();
  return (
    <RadioGroup defaultValue="email" aria-label="Two-step sign-in">
      {options.map((option) => (
        <div key={option.value} className="flex items-center gap-3">
          <RadioGroupItem id={`${id}-${option.value}`} value={option.value} disabled={option.disabled} />
          <label
            htmlFor={`${id}-${option.value}`}
            className={option.disabled ? "text-sm text-muted-foreground" : "text-sm"}
          >
            {option.label} {option.hint}
          </label>
        </div>
      ))}
    </RadioGroup>
  );
}
```

## Related

- [Checkbox](https://ui.fewtone.com/components/checkbox.md): An accessible React checkbox for Next.js forms, with a check that draws itself in and an indeterminate state, built on Radix Checkbox.
- [Switcher](https://ui.fewtone.com/components/switcher.md): An accessible React segmented control for Next.js: one choice from a few options, with a pill that slides to the one you pick.
- [Switch](https://ui.fewtone.com/components/switch.md): An accessible React switch for on/off settings, with a spring-animated thumb, built on Radix Switch.
