# RadioGroup

> Pick exactly one option from a short list, as plain radios or as bordered cards for plan-style choices.

Source: https://docs.nasaqui.com/components/radio-group

## Install

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

A set of mutually exclusive options. `RadioGroup` owns the value and keyboard behaviour, `Radio` is the round
control, and `RadioCard` is the card variant where the whole bordered card is the radio (pricing plans, billing
periods, delivery methods). Built on Base UI `RadioGroup` and `Radio`, so it works inside forms and inside `Field`.

## When to use

- Choosing one of two to five options where seeing all of them helps.
- Plan or tier selection: use `RadioCard`.

## When not to use

- Many options: use `Select`.
- Several may be chosen: use `Checkbox`.
- A setting that applies immediately: use `Switch`.
- A compact segmented choice such as a view mode: use `ToggleGroup`.

## Import

```tsx
import { RadioGroup, Radio, RadioCard } from "@fadymondy/nasaq/web";
```

## Quick start

```tsx
import { Field, FieldDescription, FieldLabel, Radio, RadioGroup } from "@fadymondy/nasaq/web";

export function Contact() {
  return (
    <Field name="contact">
      <FieldLabel>Contact by</FieldLabel>
      <FieldDescription>We only use this for account notices.</FieldDescription>
      <RadioGroup defaultValue="email" aria-label="Contact by">
        <label className="flex items-center gap-2 text-body-sm">
          <Radio value="email" /> Email
        </label>
        <label className="flex items-center gap-2 text-body-sm">
          <Radio value="sms" /> SMS
        </label>
      </RadioGroup>
    </Field>
  );
}
```

## Anatomy

```
<RadioGroup>            data-slot="radio-group"
├─ <Radio>              data-slot="radio"        (with <label>)
│  └─ indicator         data-slot="radio-indicator"
└─ <RadioCard>          data-slot="radio-card"
   ├─ mark              data-slot="radio-card-mark"
   ├─ title             data-slot="radio-card-title"
   ├─ description       data-slot="radio-card-description"
   └─ meta              data-slot="radio-card-meta"
```

## API

### RadioGroup

All Base UI `RadioGroup` props.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` / `defaultValue` | `unknown` | none | Controlled or uncontrolled selected value. |
| `onValueChange` | `(value, eventDetails) => void` | none | Called when the selection changes. |
| `name` | `string` | none | Form field name. |
| `disabled`, `readOnly`, `required` | `boolean` | `false` | Form behaviour. |

### Radio

All Base UI `Radio.Root` props. `value` is required.

### RadioCard (`RadioCardProps`)

All Base UI `Radio.Root` props except `children` and the HTML `title`, plus:

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `ReactNode` | required | The option's name. |
| `description` | `ReactNode` | none | Supporting text. |
| `meta` | `ReactNode` | none | Content at the inline end, such as a price. |

## Examples

Plan choice with cards:

```tsx
import { RadioCard, RadioGroup } from "@fadymondy/nasaq/web";

export function Plans() {
  return (
    <RadioGroup defaultValue="pro" aria-label="Plan">
      <RadioCard value="free" title="Free" description="For trying it out" meta="$0" />
      <RadioCard value="pro" title="Pro" description="For growing teams" meta="$12" />
    </RadioGroup>
  );
}
```

Arabic:

```tsx
import { Radio, RadioGroup } from "@fadymondy/nasaq/web";

export function ContactAr() {
  return (
    <RadioGroup defaultValue="email" aria-label="طريقة التواصل">
      <label className="flex items-center gap-2 text-body-sm">
        <Radio value="email" /> البريد الإلكتروني
      </label>
      <label className="flex items-center gap-2 text-body-sm">
        <Radio value="sms" /> رسالة نصية
      </label>
    </RadioGroup>
  );
}
```

## Accessibility

| Key | Action |
| --- | --- |
| Tab | Moves focus into the group (to the selected radio, or the first). |
| Arrow keys | Move to and select the next or previous radio. In RTL, ArrowLeft goes to the next one. |
| Space | Selects the focused radio. |

- `RadioGroup` has `role="radiogroup"`, each radio `role="radio"` with `aria-checked`.
- Name the group (`FieldLabel`, `aria-labelledby` or `aria-label`), and localise that label.
- A `RadioCard` takes its accessible name from its title and description.

## RTL & i18n

The card mirrors: the mark sits at the inline start and `meta` at the inline end. Layout uses logical classes only.
Arrow-key direction follows the `NasaqProvider` direction.

## Styling & tokens

Border `border-nq-line-strong`, checked `bg-primary` / `border-primary`, card selected `bg-nq-selected`, focus
`nq-focus`. State attributes: `data-checked`, `data-unchecked`, `data-disabled`. `className` merges onto the root.

## Do / Don't

- Do preselect a sensible default when one option is almost always right.
- Don't use a radio group for a single on/off choice.

## Related

[checkbox](https://docs.nasaqui.com/components/checkbox), [switch](https://docs.nasaqui.com/components/switch), [field](https://docs.nasaqui.com/components/field), [toggle-group](https://docs.nasaqui.com/components/toggle-group).

## Lab

https://docs.nasaqui.com/?path=/docs/components-forms-radio-group--docs

## Code

### React

```tsx
import { Field, FieldDescription, FieldLabel, Radio, RadioGroup } from "@fadymondy/nasaq/web";

export function Contact() {
  return (
    <Field name="contact">
      <FieldLabel>Contact by</FieldLabel>
      <FieldDescription>We only use this for account notices.</FieldDescription>
      <RadioGroup defaultValue="email" aria-label="Contact by">
        <label className="flex items-center gap-2 text-body-sm">
          <Radio value="email" /> Email
        </label>
        <label className="flex items-center gap-2 text-body-sm">
          <Radio value="sms" /> SMS
        </label>
      </RadioGroup>
    </Field>
  );
}
```

### shadcn

```tsx
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field";
import { Radio, RadioGroup } from "@/components/ui/radio-group";

export function Contact() {
  return (
    <Field name="contact">
      <FieldLabel>Contact by</FieldLabel>
      <FieldDescription>We only use this for account notices.</FieldDescription>
      <RadioGroup defaultValue="email" aria-label="Contact by">
        <label className="flex items-center gap-2 text-body-sm">
          <Radio value="email" /> Email
        </label>
        <label className="flex items-center gap-2 text-body-sm">
          <Radio value="sms" /> SMS
        </label>
      </RadioGroup>
    </Field>
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { NqField, NqFieldDescription, NqFieldLabel, NqRadio, NqRadioGroup } from "@fadymondy/nasaq/vue";
</script>

<template>
  <NqField name="contact">
    <NqFieldLabel>Contact by</NqFieldLabel>
    <NqFieldDescription>We only use this for account notices.</NqFieldDescription>
    <NqRadioGroup default-value="email" aria-label="Contact by">
      <label class="flex items-center gap-2 text-body-sm">
        <NqRadio value="email" /> Email
      </label>
      <label class="flex items-center gap-2 text-body-sm">
        <NqRadio value="sms" /> SMS
      </label>
    </NqRadioGroup>
  </NqField>
</template>
```

### Blade

```blade
<x-nq::field name="contact">
    <x-nq::field.label>Contact by</x-nq::field.label>
    <x-nq::field.description>We only use this for account notices.</x-nq::field.description>
    <x-nq::radio-group default-value="email" aria-label="Contact by">
        <label class="flex items-center gap-2 text-body-sm">
            <x-nq::radio-group.radio value="email" /> Email
        </label>
        <label class="flex items-center gap-2 text-body-sm">
            <x-nq::radio-group.radio value="sms" /> SMS
        </label>
    </x-nq::radio-group>
</x-nq::field>
```

### HTML + Alpine

```html
<div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     class="flex flex-col gap-1.5">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Contact by</label>
    <p data-slot="field-description" class="text-caption text-muted-foreground">We only use this for account notices.</p>
    <div role="radiogroup" data-slot="radio-group" aria-orientation="vertical" x-data="nqRadioGroup('email')" x-modelable="value" x-bind="root"
                aria-label="Contact by" class="flex flex-col gap-2">
    <label class="flex items-center gap-2 text-body-sm">
            <button type="button" role="radio" data-slot="radio" x-bind="radio('email')"
    aria-checked="true" tabindex="0"
     data-checked         class="relative inline-flex size-4 shrink-0 items-center justify-center rounded-full border border-nq-line-strong bg-card outline-none transition-colors duration-150 ease-nq data-checked:border-primary data-checked:bg-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:cursor-not-allowed data-disabled:opacity-50 after:absolute after:-inset-1 after:rounded-full">
    <span data-slot="radio-indicator" class="block size-1.5 rounded-full bg-primary-foreground" x-show="isChecked('email')" ></span>
</button>
 Email
        </label>
        <label class="flex items-center gap-2 text-body-sm">
            <button type="button" role="radio" data-slot="radio" x-bind="radio('sms')"
    aria-checked="false" tabindex="-1"
     data-unchecked         class="relative inline-flex size-4 shrink-0 items-center justify-center rounded-full border border-nq-line-strong bg-card outline-none transition-colors duration-150 ease-nq data-checked:border-primary data-checked:bg-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:cursor-not-allowed data-disabled:opacity-50 after:absolute after:-inset-1 after:rounded-full">
    <span data-slot="radio-indicator" class="block size-1.5 rounded-full bg-primary-foreground" x-show="isChecked('sms')"  style="display: none" ></span>
</button>
 SMS
        </label>
    </div>
</div>
```
