# Radio Group

Picks exactly one option from a small set, with every option visible at once.

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

function RadioGroupDemo() {
  return (
    <RadioGroup
      defaultValue="daily"
      aria-label="Scan frequency"
      className="w-fit"
    >
      <Field orientation="horizontal">
        <RadioGroupItem value="hourly" id="frequency-hourly" />
        <FieldLabel htmlFor="frequency-hourly">Hourly</FieldLabel>
      </Field>
      <Field orientation="horizontal">
        <RadioGroupItem value="daily" id="frequency-daily" />
        <FieldLabel htmlFor="frequency-daily">Daily</FieldLabel>
      </Field>
      <Field orientation="horizontal">
        <RadioGroupItem value="weekly" id="frequency-weekly" />
        <FieldLabel htmlFor="frequency-weekly">Weekly</FieldLabel>
      </Field>
    </RadioGroup>
  )
}
```

## Usage

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

<RadioGroup defaultValue="daily" aria-label="Scan frequency">
  <Field orientation="horizontal">
    <RadioGroupItem value="hourly" id="frequency-hourly" />
    <FieldLabel htmlFor="frequency-hourly">Hourly</FieldLabel>
  </Field>
  <Field orientation="horizontal">
    <RadioGroupItem value="daily" id="frequency-daily" />
    <FieldLabel htmlFor="frequency-daily">Daily</FieldLabel>
  </Field>
</RadioGroup>
```

Use a radio group for two to five options that the user should compare before choosing, and
when one option is always selected. Set `defaultValue` so the group never starts empty.

For more options, or when the choice is secondary, use a [Select](/components/select). For
options that can be combined, use [Checkboxes](/components/checkbox). For a choice that
changes a view right away, such as a filter, a [Toggle Group](/components/toggle-group) is more
compact.

Lay out each option as a horizontal [Field](/components/field) with a `FieldLabel` pointing at
the item's `id`. The group is a vertical grid with an 8px gap; override it with `className`.

## Examples

### With a description

Put the group in a `FieldSet` with a `FieldLegend` that names the choice, and give each option
a `FieldDescription` in a `FieldContent`.

```tsx
import { Field, FieldContent, FieldDescription, FieldLabel, FieldLegend, FieldSet } from "@zenoxai/ui/components/field"
import { RadioGroup, RadioGroupItem } from "@zenoxai/ui/components/radio-group"

function RadioGroupWithDescription() {
  return (
    <FieldSet className="max-w-sm">
      <FieldLegend variant="label">When an alert is resolved</FieldLegend>
      <RadioGroup defaultValue="notify-owner">
        <Field orientation="horizontal">
          <RadioGroupItem value="notify-owner" id="resolve-owner" />
          <FieldContent>
            <FieldLabel htmlFor="resolve-owner">Notify the owner</FieldLabel>
            <FieldDescription>
              Only the assigned analyst gets an email.
            </FieldDescription>
          </FieldContent>
        </Field>
        <Field orientation="horizontal">
          <RadioGroupItem value="notify-team" id="resolve-team" />
          <FieldContent>
            <FieldLabel htmlFor="resolve-team">Notify the team</FieldLabel>
            <FieldDescription>
              Everyone on the tenant's SOC team gets an email.
            </FieldDescription>
          </FieldContent>
        </Field>
        <Field orientation="horizontal">
          <RadioGroupItem value="silent" id="resolve-silent" />
          <FieldContent>
            <FieldLabel htmlFor="resolve-silent">Do not notify</FieldLabel>
            <FieldDescription>
              The change appears in the alert history only.
            </FieldDescription>
          </FieldContent>
        </Field>
      </RadioGroup>
    </FieldSet>
  )
}
```

### Choice cards

Wrap each option's `Field` in a `FieldLabel` to turn it into a bordered card. The whole card is
clickable, and the selected card is highlighted. Use `FieldTitle` for the option name inside
the card, since the card itself is the label.

```tsx
import { Field, FieldContent, FieldDescription, FieldLabel, FieldTitle } from "@zenoxai/ui/components/field"
import { RadioGroup, RadioGroupItem } from "@zenoxai/ui/components/radio-group"

function RadioGroupChoiceCards() {
  return (
    <RadioGroup
      defaultValue="external"
      aria-label="Scan type"
      className="max-w-sm"
    >
      <FieldLabel htmlFor="scan-external">
        <Field orientation="horizontal">
          <FieldContent>
            <FieldTitle>External scan</FieldTitle>
            <FieldDescription>
              Internet-facing domains and IPs. About 10 minutes.
            </FieldDescription>
          </FieldContent>
          <RadioGroupItem value="external" id="scan-external" />
        </Field>
      </FieldLabel>
      <FieldLabel htmlFor="scan-internal">
        <Field orientation="horizontal">
          <FieldContent>
            <FieldTitle>Internal scan</FieldTitle>
            <FieldDescription>
              Hosts behind the network agent. About 1 hour.
            </FieldDescription>
          </FieldContent>
          <RadioGroupItem value="internal" id="scan-internal" />
        </Field>
      </FieldLabel>
    </RadioGroup>
  )
}
```

### Disabled

`disabled` on a `RadioGroupItem` disables one option; on the `RadioGroup` it disables them all.
Set `data-disabled` on the option's `Field` to dim its label. Say in the label why the option
is not available.

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

function RadioGroupDisabled() {
  return (
    <RadioGroup defaultValue="30" aria-label="Log retention" className="w-fit">
      <Field orientation="horizontal">
        <RadioGroupItem value="30" id="retention-30" />
        <FieldLabel htmlFor="retention-30">30 days</FieldLabel>
      </Field>
      <Field orientation="horizontal">
        <RadioGroupItem value="90" id="retention-90" />
        <FieldLabel htmlFor="retention-90">90 days</FieldLabel>
      </Field>
      <Field orientation="horizontal" data-disabled>
        <RadioGroupItem value="365" id="retention-365" disabled />
        <FieldLabel htmlFor="retention-365">
          1 year (Enterprise plan)
        </FieldLabel>
      </Field>
    </RadioGroup>
  )
}
```

### Controlled

Pass `value` and `onValueChange`. `RadioGroup` is not generic, so `onValueChange` receives
`unknown`; cast it to the type of your values.

```tsx
import * as React from "react"
import { Field, FieldLabel } from "@zenoxai/ui/components/field"
import { RadioGroup, RadioGroupItem } from "@zenoxai/ui/components/radio-group"

function RadioGroupControlled() {
  const [view, setView] = React.useState("open")

  return (
    <div className="grid justify-items-center gap-3">
      <RadioGroup
        value={view}
        onValueChange={(value) => setView(value as string)}
        aria-label="Alerts to show"
        className="flex w-fit gap-4"
      >
        <Field orientation="horizontal" className="w-auto">
          <RadioGroupItem value="open" id="view-open" />
          <FieldLabel htmlFor="view-open">Open</FieldLabel>
        </Field>
        <Field orientation="horizontal" className="w-auto">
          <RadioGroupItem value="mine" id="view-mine" />
          <FieldLabel htmlFor="view-mine">Assigned to me</FieldLabel>
        </Field>
        <Field orientation="horizontal" className="w-auto">
          <RadioGroupItem value="all" id="view-all" />
          <FieldLabel htmlFor="view-all">All</FieldLabel>
        </Field>
      </RadioGroup>
      <p className="text-sm text-text-secondary">Value: {view}</p>
    </div>
  )
}
```

## Accessibility

- The group has `role="radiogroup"` and needs a name: `aria-label`, or `aria-labelledby`
  pointing at a visible heading. A `FieldLegend` names the surrounding `FieldSet`, not the
  group itself.
- Tab moves focus into the group to the selected option and out again in one step.
  Arrow keys move between options and select them.
- Each item renders a `<span role="radio">` with a hidden `<input>` beside it. The `id` goes to
  the hidden input, so `FieldLabel htmlFor` names the item and selects it on click.
- `name` on `RadioGroup` submits the selected value with a native form.

## API reference

`import { ... } from "@zenoxai/ui/components/radio-group"`

### RadioGroup

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| defaultValue | `any` |  | The uncontrolled value of the radio button that should be initially selected.  To render a controlled radio group, use the `value` prop instead. |
| disabled | `boolean` | `false` | Whether the component should ignore user interaction. |
| form | `string` |  | Identifies the form that owns the radio inputs. Useful when the radio group is rendered outside the form. |
| inputRef | `Ref<HTMLInputElement>` |  | A ref to access the hidden input element. |
| name | `string` |  | Identifies the field when a form is submitted. |
| onValueChange | `((value: any, eventDetails: { reason: "none"; event: Event; cancel: () => void; allowPropagation: () => void; isCanceled: boolean; isPropagationAllowed: boolean; trigger: Element; }) => void)` |  | Callback fired when the value changes. |
| readOnly | `boolean` | `false` | Whether the user should be unable to select a different radio button in the group. |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, RadioGroupState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |
| required | `boolean` | `false` | Whether the user must choose a value before submitting a form. |
| value | `any` |  | The controlled value of the radio item that should be currently selected.  To render an uncontrolled radio group, use the `defaultValue` prop instead. |

### RadioGroupItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| disabled | `boolean` |  | Whether the component should ignore user interaction. |
| inputRef | `Ref<HTMLInputElement>` |  | A ref to access the hidden input element. |
| nativeButton | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| readOnly | `boolean` |  | Whether the user should be unable to select the radio button. |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, RadioRootState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |
| required | `boolean` |  | Whether the user must choose a value before submitting a form. |
| value (required) | `any` |  | The unique identifying value of the radio in a group. |

