# Radio (/ui/components/react/radio)



<Playground component="radio" />

## Usage [#usage]

```tsx
import { RadioGroup } from '@appica/ui-react/radio-group'
import { Radio } from '@appica/ui-react/radio'
```

```tsx
<RadioGroup defaultValue="pro" aria-label="Plan">
  <label className="flex items-center gap-2">
    <Radio value="free" />
    Free
  </label>
  <label className="flex items-center gap-2">
    <Radio value="pro" />
    Pro
  </label>
  <label className="flex items-center gap-2">
    <Radio value="team" />
    Team
  </label>
</RadioGroup>
```

A `Radio` never stands alone — it only works inside a [`RadioGroup`](#radiogroup), which owns the selected `value` and the roving focus that lets arrow keys move between options. Each radio is identified by its `value`; that's what selection reports back. Wrap each radio and its text in a `<label>` so the text is clickable and announced together. The fill/squish animation is skipped when the user prefers reduced motion.

## Examples [#examples]

### Default [#default]

Set the initial selection with `defaultValue` (uncontrolled) on the group, or drive it yourself with `value` + `onValueChange`. The group lays its options out in a column by default.

```tsx
import { RadioGroup } from '@appica/ui-react/radio-group'
import { Radio } from '@appica/ui-react/radio'

const plans = [
  { value: 'free', label: 'Free' },
  { value: 'pro', label: 'Pro' },
  { value: 'team', label: 'Team' },
]

export default function RadioGroupExample() {
  return (
    <RadioGroup defaultValue="pro" aria-labelledby="plan-label">
      <span id="plan-label" className="text-foreground mb-1 text-sm font-medium">
        Plan
      </span>
      {plans.map((plan) => (
        <label key={plan.value} className="flex items-center gap-2 text-sm select-none">
          <Radio value={plan.value} />
          {plan.label}
        </label>
      ))}
    </RadioGroup>
  )
}
```

### Horizontal [#horizontal]

Pass `orientation="horizontal"` to lay the options out in a row. This also switches the arrow keys that move selection from ↑/↓ to ←/→.

```tsx
import { RadioGroup } from '@appica/ui-react/radio-group'
import { Radio } from '@appica/ui-react/radio'

const sizes = [
  { value: 's', label: 'Small' },
  { value: 'm', label: 'Medium' },
  { value: 'l', label: 'Large' },
]

export default function RadioGroupOrientation() {
  return (
    <RadioGroup orientation="horizontal" defaultValue="m" aria-label="Size">
      {sizes.map((size) => (
        <label key={size.value} className="flex items-center gap-2 text-sm select-none">
          <Radio value={size.value} />
          {size.label}
        </label>
      ))}
    </RadioGroup>
  )
}
```

### Sizing [#sizing]

`Radio` is sized in `em`, so it scales with the surrounding font size — set a `text-*` class on the wrapping `<label>` and the control and text grow together. It won't shrink below a 16px floor.

```tsx
import { RadioGroup } from '@appica/ui-react/radio-group'
import { Radio } from '@appica/ui-react/radio'

export default function RadioSizing() {
  return (
    <RadioGroup defaultValue="base" aria-label="Size" className="items-start gap-4 select-none">
      <label className="flex items-center gap-2 text-base">
        <Radio value="base" />
        text-base
      </label>
      <label className="flex items-center gap-2.5 text-lg">
        <Radio value="lg" />
        text-lg
      </label>
      <label className="flex items-center gap-3 text-xl">
        <Radio value="xl" />
        text-xl
      </label>
    </RadioGroup>
  )
}
```

### Disabled [#disabled]

Disable the whole set with `disabled` on the `RadioGroup`, or a single option with `disabled` on its `Radio`. A disabled option is skipped by arrow-key navigation.

```tsx
import { RadioGroup } from '@appica/ui-react/radio-group'
import { Radio } from '@appica/ui-react/radio'

export default function RadioGroupDisabled() {
  return (
    <div className="flex flex-col gap-8">
      <RadioGroup defaultValue="standard" aria-label="Shipping">
        <label className="flex items-center gap-2 text-sm select-none">
          <Radio value="standard" />
          Standard
        </label>
        <label className="flex items-center gap-2 text-sm select-none">
          <Radio value="express" />
          Express
        </label>
        <label className="text-foreground-muted flex items-center gap-2 text-sm select-none">
          <Radio value="overnight" disabled />
          Overnight (unavailable)
        </label>
      </RadioGroup>

      <RadioGroup defaultValue="yearly" disabled aria-label="Billing period">
        <label className="text-foreground-muted flex items-center gap-2 text-sm select-none">
          <Radio value="monthly" />
          Monthly
        </label>
        <label className="text-foreground-muted flex items-center gap-2 text-sm select-none">
          <Radio value="yearly" />
          Yearly
        </label>
      </RadioGroup>
    </div>
  )
}
```

### With descriptions [#with-descriptions]

Pair each option's label with supporting text, aligning the radio to the first line. Keeping it all inside the `<label>` makes the whole row one target.

```tsx
import { RadioGroup } from '@appica/ui-react/radio-group'
import { Radio } from '@appica/ui-react/radio'

const visibilities = [
  { value: 'public', label: 'Public', description: 'Anyone on the internet can see this project.' },
  { value: 'private', label: 'Private', description: 'Only you and people you invite can access it.' },
  { value: 'team', label: 'Team', description: 'Everyone in your organization can view and edit.' },
]

export default function RadioGroupWithDescriptions() {
  return (
    <RadioGroup defaultValue="private" aria-label="Visibility" className="max-w-md">
      {visibilities.map((visibility) => (
        <label key={visibility.value} className="flex items-start gap-2.5 select-none">
          <Radio value={visibility.value} className="mt-1" />
          <span className="flex flex-col">
            <span className="text-foreground-intense font-medium">{visibility.label}</span>
            <span className="text-foreground-muted text-sm">{visibility.description}</span>
          </span>
        </label>
      ))}
    </RadioGroup>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior — roving focus, popup placement, and the like — follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).

<RtlPreview component="radio" />

## API reference [#api-reference]

`RadioGroup` and `Radio` wrap [Base UI's Radio](https://base-ui.com/react/components/radio). The group renders a `<div role="radiogroup">` and each `Radio` a `role="radio"`; both forward `ref`, `aria-*`, and their native attributes, and expose `data-*` state attributes (`data-checked`, `data-unchecked`, `data-disabled`, `data-readonly`, …) for styling. See the Base UI docs for the complete API.

### RadioGroup [#radiogroup]

The container that owns the selected value and keyboard navigation.

| Prop            | <ColMinWidth width="200">Type</ColMinWidth>                   | Default      | <ColMinWidth width="220">Description</ColMinWidth>                                    |
| --------------- | ------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------- |
| `value`         | `string`                                                      | —            | Controlled selected value. Pair with `onValueChange`.                                 |
| `defaultValue`  | `string`                                                      | —            | Uncontrolled initial selection.                                                       |
| `onValueChange` | <Code>(value: string, details) => void</Code>                 | —            | Fires when the selection changes.                                                     |
| `orientation`   | <Code>'horizontal' \| 'vertical'</Code>                       | `'vertical'` | Lay options out in a row or column; sets which arrow keys move selection.             |
| `name`          | `string`                                                      | —            | Field name submitted with a form.                                                     |
| `disabled`      | `boolean`                                                     | `false`      | Disables every radio in the group.                                                    |
| `readOnly`      | `boolean`                                                     | `false`      | Focusable, but the selection can't be changed.                                        |
| `required`      | `boolean`                                                     | `false`      | An option must be chosen before the form submits.                                     |
| `render`        | <Code>ReactElement \| ((props, state) => ReactElement)</Code> | —            | Replace the group element, or compose it with another component.                      |
| `className`     | <Code>string \| ((state) => string)</Code>                    | —            | Extra classes, merged via `tailwind-merge` (override the default `flex` layout here). |
| `style`         | <Code>CSSProperties \| ((state) => CSSProperties)</Code>      | —            | Inline styles, optionally derived from state.                                         |

### Radio [#radio]

One option inside a `RadioGroup`.

| Prop           | <ColMinWidth width="200">Type</ColMinWidth>                   | Default | <ColMinWidth width="220">Description</ColMinWidth>                                   |
| -------------- | ------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------ |
| `value`        | `string`                                                      | —       | **Required.** The value this option selects; reported by the group on change.        |
| `disabled`     | `boolean`                                                     | `false` | Removes this option from the tab order and skips it during arrow navigation.         |
| `readOnly`     | `boolean`                                                     | `false` | Focusable but not selectable.                                                        |
| `required`     | `boolean`                                                     | `false` | Marks this option required for form submission.                                      |
| `nativeButton` | `boolean`                                                     | `false` | Whether the element passed to `render` is a native `<button>`.                       |
| `inputRef`     | <Code>Ref\<HTMLInputElement></Code>                           | —       | Ref to the hidden `<input>` element backing the radio.                               |
| `id`           | `string`                                                      | —       | `id` of the rendered element.                                                        |
| `render`       | <Code>ReactElement \| ((props, state) => ReactElement)</Code> | —       | Replace the rendered element, or compose it with another component.                  |
| `className`    | <Code>string \| ((state) => string)</Code>                    | —       | Extra classes on the radio, merged via `tailwind-merge`. May be a function of state. |
| `style`        | <Code>CSSProperties \| ((state) => CSSProperties)</Code>      | —       | Inline styles, optionally derived from state.                                        |

## Accessibility [#accessibility]

* `RadioGroup` renders a `<div role="radiogroup">` and each `Radio` a `role="radio"`. Name the group with `aria-label` or `aria-labelledby`.
* The group is a single tab stop: **Tab** moves focus into the selected option (or the first one), and the **arrow keys** move the selection between options — ↑/↓ when vertical, ←/→ when horizontal — wrapping around the ends.
* Each radio needs an accessible name — wrap it in a `<label>` together with its text, or give it an `aria-label`.
* `disabled` on a radio or the group removes the affected options from the tab order and exposes `data-disabled` for styling; disabled options are skipped during arrow navigation.
* The fill/squish animation honours `prefers-reduced-motion`.
