# Radio Group

> A single choice from a list.

## Examples

### Basic

```vue
<script setup lang="ts">
const plan = ref("pro");
const plans = [
  { label: "Hobby — free", value: "hobby" },
  { label: "Pro — $20/month", value: "pro" },
  { label: "Enterprise — contact us", value: "enterprise", disabled: true },
];
</script>

<template>
  <UiRadioGroup v-model="plan" :options="plans" label="Plan" hint="You can change it any time." />
</template>
```

### Horizontal

```vue
<script setup lang="ts">
const size = ref("m");
const sizes = ["xs", "s", "m", "l", "xl"].map((value) => ({ label: value.toUpperCase(), value }));
</script>

<template>
  <UiRadioGroup v-model="size" :options="sizes" label="Size" orientation="horizontal" />
</template>
```

### Custom options

The `option` slot replaces each radio's content. Include `radio-control` to keep the circle.

```vue
<script setup lang="ts">
const plan = ref("pro");
const plans = [
  { label: "Hobby", value: "hobby", price: "Free", detail: "One project, community support" },
  { label: "Pro", value: "pro", price: "$20/mo", detail: "Unlimited projects, email support" },
  { label: "Team", value: "team", price: "$60/mo", detail: "Shared workspaces, SSO" },
];
</script>

<template>
  <UiRadioGroup v-model="plan" :options="plans" label="Plan" class="plan-cards">
    <template #option="{ option }">
      <span data-part="radio-control" aria-hidden="true" />
      <span class="plan-text">
        <strong>{{ option.label }}</strong>
        <small>{{ plans.find((p) => p.value === option.value)?.detail }}</small>
      </span>
      <span class="plan-price">{{ plans.find((p) => p.value === option.value)?.price }}</span>
    </template>
  </UiRadioGroup>
</template>
```

## API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `options` | `SelectOption<T>[]` | `[]` | Radios to render, in order. |
| `label` | `string` | — | Visible group label, which names the radiogroup. |
| `hint` | `string` | — | Help text under the control, announced as its description. |
| `error` | `string \| boolean` | — | `true` marks the control invalid; a string also shows it as the message, replacing the hint. |
| `disabled` | `boolean` | `false` | Disables the control. |
| `orientation` | `"horizontal" \| "vertical"` | `"vertical"` | Layout hook, exposed as `data-orientation` and `aria-orientation`. |
| `name` | `string` | — | Form field name: submits the value with a native form, via hidden inputs. |
| `id` | `string` | — | Id for the control; generated with `useId()` when omitted. |
| `modelValue` | `T` | — | The selected value. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:modelValue` | `[value: T \| undefined]` | `v-model:modelValue` update. |

| Slot | Props | Description |
| --- | --- | --- |
| `option` | `{ option: SelectOption<T>; checked: boolean; }` | Radio content, replacing the control and label. |

Types:

```ts
export interface SelectOption<T extends SelectValue = SelectValue> {
  label: string;
  value: T;
  disabled?: boolean;
}

export type SelectValue = string | number;
```

## Styling

- Parts (`[data-part="…"]`): `field`, `field-label`, `radio-group`, `radio`, `radio-control`, `radio-label`, `field-message`
- State attributes: `data-field-error`, `data-field-name`, `data-orientation`, `data-radio-checked`, `data-field-tone`

## Keyboard

| Keys           | Action                                     |
| -------------- | ------------------------------------------ |
| `Tab`          | Focus the selected radio                   |
| Arrow keys     | Select the next / previous radio, wrapping |
| `Home` / `End` | Select the first / last radio              |

## Accessibility

- In right-to-left layouts, `←` / `→` follow the visual direction.
