# Radio Group

A group of options where the user selects one value.

```tsx
import { Field, Label, RadioGroup, RadioGroupItem } from 'ferry-ui'

export default function RadioGroupHero() {
  return (
    <Field label="Billing period" labelAs="span" hint="The new period starts with the next invoice.">
      <RadioGroup defaultValue="monthly">
        <Label className="font-normal">
          <RadioGroupItem value="monthly" />
          Monthly
        </Label>
        <Label className="font-normal">
          <RadioGroupItem value="yearly" />
          Yearly, with two months free
        </Label>
      </RadioGroup>
    </Field>
  )
}
```

## Usage guidelines

- **One value among 2 to 5 options.** All the options stay in view.
- **Give the group a name.** Pass `aria-label` or `aria-labelledby`. A [Field](/docs/components/field) with `labelAs="span"` adds a visible label.
- **Give each option a label.** Wrap each `RadioGroupItem` and its text in a [Label](/docs/components/label).

| Choice | Control |
| --- | --- |
| One value among 2 to 5 options | `RadioGroup` |
| One value among 4 to 15 options | [Select](/docs/components/select) |
| One value among 2 to 6 options with a description or an icon | [Radio Card Group](/docs/components/radio-card-group) |
| An on/off choice | [Switch](/docs/components/switch) or [Checkbox](/docs/components/checkbox) |
| A compact control to change the view or the format | [Toggle Group](/docs/components/toggle-group) |

## Anatomy

Import the two parts. `RadioGroup` holds the value. Each `RadioGroupItem` is one option.

```tsx title="Anatomy"

<RadioGroup>
  <RadioGroupItem />
</RadioGroup>
```

## Examples

### Row

The group puts the options in a column by default. For a row, pass `className="flex gap-4"`.

```tsx
import { Label, RadioGroup, RadioGroupItem } from 'ferry-ui'

export default function RadioGroupRow() {
  return (
    <RadioGroup aria-label="Export format" defaultValue="csv" className="flex gap-4">
      <Label className="font-normal">
        <RadioGroupItem value="csv" />
        CSV
      </Label>
      <Label className="font-normal">
        <RadioGroupItem value="pdf" />
        PDF
      </Label>
      <Label className="font-normal">
        <RadioGroupItem value="json" />
        JSON
      </Label>
    </RadioGroup>
  )
}
```

### Descriptions

To add a description to an option, link the `Label` with `htmlFor`. Link the description with `aria-describedby`.

```tsx
import { Label, RadioGroup, RadioGroupItem } from 'ferry-ui'

const ROLES = [
  { value: 'viewer', label: 'Viewer', description: 'Sees the projects and the reports.' },
  { value: 'member', label: 'Member', description: 'Creates and edits the projects of the workspace.' },
  { value: 'admin', label: 'Admin', description: 'Has full access, with billing and members.' },
]

export default function RadioGroupDescriptions() {
  return (
    <RadioGroup aria-label="Role" defaultValue="member" className="max-w-sm gap-4">
      {ROLES.map((role) => (
        <div key={role.value} className="flex items-start gap-2.5">
          <RadioGroupItem
            value={role.value}
            id={`role-${role.value}`}
            aria-describedby={`role-${role.value}-hint`}
            className="mt-0.5"
          />
          <div className="flex flex-col gap-0.5">
            <Label htmlFor={`role-${role.value}`}>{role.label}</Label>
            <p id={`role-${role.value}-hint`} className="text-[13px] text-foreground-light">
              {role.description}
            </p>
          </div>
        </div>
      ))}
    </RadioGroup>
  )
}
```

### Disabled option

`disabled` on a `RadioGroupItem` disables one option. `disabled` on `RadioGroup` disables all the options.

```tsx
import { Label, RadioGroup, RadioGroupItem } from 'ferry-ui'

export default function RadioGroupDisabled() {
  return (
    <RadioGroup aria-label="Billing period" defaultValue="monthly">
      <Label className="font-normal">
        <RadioGroupItem value="monthly" />
        Monthly
      </Label>
      <Label className="font-normal">
        <RadioGroupItem value="yearly" />
        Yearly
      </Label>
      <Label className="font-normal">
        <RadioGroupItem value="custom" disabled />
        Custom contract, on the Enterprise plan only
      </Label>
    </RadioGroup>
  )
}
```

### Controlled value

A radio group holds its value by default. Use `defaultValue` for the first value.

To control the value, pass `value` and `onValueChange`.

```tsx
import * as React from 'react'
import { Label, RadioGroup, RadioGroupItem } from 'ferry-ui'

const PLANS = [
  { value: 'starter', label: 'Starter', price: '$0' },
  { value: 'pro', label: 'Pro', price: '$29' },
  { value: 'team', label: 'Team', price: '$99' },
]

export default function RadioGroupControlled() {
  const [plan, setPlan] = React.useState('pro')
  const price = PLANS.find((item) => item.value === plan)?.price

  return (
    <div className="flex flex-col gap-4">
      <RadioGroup aria-label="Plan" value={plan} onValueChange={setPlan}>
        {PLANS.map((item) => (
          <Label key={item.value} className="font-normal">
            <RadioGroupItem value={item.value} />
            {item.label}
          </Label>
        ))}
      </RadioGroup>
      <p className="text-[13px] text-foreground-light">Price: {price} each month</p>
    </div>
  )
}
```

### Error

`aria-invalid` on each `RadioGroupItem` shows the error border. The `error` prop of `Field` shows the message as text.

```tsx
import * as React from 'react'
import { Button, Field, Label, RadioGroup, RadioGroupItem, toast } from 'ferry-ui'

const FORMATS = ['CSV', 'PDF', 'JSON']

export default function RadioGroupInvalid() {
  const [format, setFormat] = React.useState('')
  const [error, setError] = React.useState<string>()

  function submit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
    setError(format === '' ? 'Select an export format.' : undefined)
    if (format !== '') toast.success('Export started')
  }

  function change(value: string) {
    setFormat(value)
    setError(undefined)
  }

  return (
    <form noValidate onSubmit={submit} className="flex flex-col items-start gap-4">
      <Field label="Export the invoices as" labelAs="span" error={error}>
        <RadioGroup value={format} onValueChange={change} className="flex gap-4">
          {FORMATS.map((name) => (
            <Label key={name} className="font-normal">
              <RadioGroupItem value={name.toLowerCase()} aria-invalid={error ? true : undefined} />
              {name}
            </Label>
          ))}
        </RadioGroup>
      </Field>
      <Button type="submit">Export</Button>
    </form>
  )
}
```

### Form submission

In a `<form>`, `name` adds the value of the group to the data of the form. `required` makes a value mandatory.

```tsx
import * as React from 'react'
import { Button, Field, Label, RadioGroup, RadioGroupItem } from 'ferry-ui'

export default function RadioGroupForm() {
  const [sent, setSent] = React.useState<string>()

  function submit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
    // The form holds one "visibility" entry: the value of the selected option.
    setSent(String(new FormData(event.currentTarget).get('visibility')))
  }

  return (
    <form onSubmit={submit} className="flex flex-col items-start gap-4">
      <Field label="Visibility of the project" labelAs="span">
        <RadioGroup name="visibility" defaultValue="private" required>
          <Label className="font-normal">
            <RadioGroupItem value="private" />
            Private
          </Label>
          <Label className="font-normal">
            <RadioGroupItem value="public" />
            Public
          </Label>
        </RadioGroup>
      </Field>
      <Button type="submit">Save</Button>
      <p role="status" className="text-[13px] text-foreground-light">
        {sent !== undefined && `The form sent: ${sent}`}
      </p>
    </form>
  )
}
```

## Accessibility

- The arrow keys move the focus to the next option or to the previous option. The option that gets the focus becomes the value.
- The group must have an accessible name.

## API reference

Each part also accepts the props of its Radix UI primitive and the attributes of its element.

### RadioGroup

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` |  |  |
| `form` | `string` |  |  |
| `required` | `boolean` |  |  |
| `disabled` | `boolean` |  |  |
| `dir` | `"ltr" \| "rtl"` |  |  |
| `orientation` | `"horizontal" \| "vertical"` |  |  |
| `loop` | `boolean` |  |  |
| `defaultValue` | `string` |  |  |
| `value` | `string \| null` |  |  |
| `onValueChange` | `((value: string) => void)` |  |  |
| `asChild` | `boolean` |  |  |

### RadioGroupItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` (required) | `string` |  |  |
| `asChild` | `boolean` |  |  |
| `checked` | `boolean` |  |  |
| `required` | `boolean` |  |  |
