# RadioGroup

> A group of round options for choosing one of 2 to 5 answers that is submitted with a form, each with room for a sentence.

Source: https://raft-design.botiverse.build/components/radio-group. Generated from the current release; where it differs from what you remember, this page is right.

A radio group offers 2 to 5 answers that exclude each other, each with a line that explains it: who gets notified, who can mention @everyone. The chosen answer has an ink dot. Radios and switches are the only round controls in the system.

```tsx
import { RadioGroup, RadioGroupItem } from "@botiverse/raft-ui";

const RESET_MODE_OPTIONS = [
  {
    value: "restart",
    label: "Restart",
    description: "Stop and restart the agent process. Runtime session and files stay intact.",
  },
  {
    value: "session",
    label: "Reset session",
    description: "Clear the runtime session and restart. Workspace files are preserved.",
  },
  {
    value: "full",
    label: "Full reset",
    description: "Clear the runtime session, delete workspace files, and restart from scratch.",
  },
] as const;

export function RadioGroupResetModeContent({
  value,
  onValueChange,
  disabled = false,
}: {
  value: string;
  onValueChange: (value: string) => void;
  disabled?: boolean;
}) {
  return (
    <RadioGroup
      aria-label="Restart mode"
      value={value}
      onValueChange={onValueChange}
      disabled={disabled}
      className="w-[22rem] max-w-full"
    >
      {RESET_MODE_OPTIONS.map((option) => (
        <label
          key={option.value}
          className="flex cursor-pointer items-start gap-2 text-left text-sm"
        >
          <RadioGroupItem value={option.value} />
          <span className="grid min-w-0 gap-1">
            <span className="text-sm font-medium text-foreground">{option.label}</span>
            <span className="text-xs leading-5 text-foreground-muted">{option.description}</span>
          </span>
        </label>
      ))}
    </RadioGroup>
  );
}
```

## When to use

| Situation                                           | Use                                                           |
| --------------------------------------------------- | ------------------------------------------------------------- |
| One of 2 to 5 answers, each worth a sentence        | RadioGroup                                                    |
| Another view of the same content, switched in place | [SegmentedControl](https://raft-design.botiverse.build/components/segmented-control.md) |
| One of many values, or values people search         | [Select](https://raft-design.botiverse.build/components/select.md)                                 |
| A yes or no that applies at once                    | [Switch](https://raft-design.botiverse.build/components/switch.md)                                 |
| A yes or no saved with a form                       | [Checkbox](https://raft-design.botiverse.build/components/checkbox.md)                           |

## Anatomy

- `RadioGroup`: the group. Holds the value. Required.
- `RadioGroupItem`: one answer's 20px circle with a 1.5px ink border, holding a hidden native input. Required, one per answer.
- `RadioGroupIndicator`: the 8px ink dot inside the chosen circle. `RadioGroupItem` renders it by default.
- A label: wrap each item, its label and its description in a `<label>`, a flex row with `items-start` and `gap-2` that carries the label's type. The circle centers on the label's first line by itself, 8px before it, at any min-height; don't add a `margin-top` ([Alignment](https://raft-design.botiverse.build/foundations/alignment.md#a-control-and-its-label)).

## Variants

| Variant                | Use for                                                                                                          |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `appearance="default"` | A list of answers, each with a circle                                                                            |
| `appearance="none"`    | An option card that shows the choice itself: the circle is visually hidden and the card draws the selected state |

```tsx
import { useCallback, useState } from "react";
import { RadioGroup, RadioGroupItem } from "@botiverse/raft-ui";
import { tv } from "tailwind-variants/lite";

const RESET_MODE_OPTIONS = [
  {
    value: "restart",
    label: "Restart",
    description: "Stop and restart the agent process. Runtime session and files stay intact.",
  },
  {
    value: "session",
    label: "Reset session",
    description: "Clear the runtime session and restart. Workspace files are preserved.",
  },
  {
    value: "full",
    label: "Full reset",
    description: "Clear the runtime session, delete workspace files, and restart from scratch.",
  },
] as const;

type RadioGroupResetModeRestoredCompositionProps = {
  value?: string;
  onValueChange?: (value: string) => void;
  disabled?: boolean;
};

const resetModeOption = tv({
  slots: {
    card: [
      "flex w-full cursor-pointer items-start text-left",
      "has-[[data-slot=radio-group-item]:focus-visible]:keyboard:ring-2 has-[[data-slot=radio-group-item]:focus-visible]:keyboard:ring-line-strong has-[[data-slot=radio-group-item]:focus-visible]:keyboard:ring-offset-2 has-[[data-slot=radio-group-item]:focus-visible]:keyboard:ring-offset-layer-sidebar",
      "has-[[data-slot=radio-group-item][data-disabled]]:cursor-not-allowed",
      "gap-3 rounded-container border border-line-hairline bg-layer-card p-4",
      "hover:overlay-subtle",
      "has-[[data-slot=radio-group-item][data-checked]]:border-line-seam has-[[data-slot=radio-group-item][data-checked]]:bg-info-soft has-[[data-slot=radio-group-item][data-checked]]:hover:bg-none",
      "has-[[data-slot=radio-group-item][data-disabled]]:border-line-hairline has-[[data-slot=radio-group-item][data-disabled]]:bg-fill-muted has-[[data-slot=radio-group-item][data-disabled]]:text-foreground-disabled has-[[data-slot=radio-group-item][data-disabled]]:hover:bg-none",
      "has-[[data-slot=radio-group-item][data-checked][data-disabled]]:border-line-hairline has-[[data-slot=radio-group-item][data-checked][data-disabled]]:bg-fill-muted",
    ],
    label: ["text-foreground", "text-subhead font-semibold"],
  },
});

export function RadioGroupResetModeRestoredComposition({
  value,
  onValueChange,
  disabled = false,
}: RadioGroupResetModeRestoredCompositionProps) {
  const [selectedValue, setSelectedValue] = useState(value ?? "restart");
  const currentValue = value ?? selectedValue;
  const styles = resetModeOption();

  const handleValueChange = useCallback(
    (nextValue: string) => {
      if (value === undefined) {
        setSelectedValue(nextValue);
      }
      onValueChange?.(nextValue);
    },
    [onValueChange, value],
  );

  return (
    <RadioGroup
      aria-label="Restart mode"
      value={currentValue}
      onValueChange={handleValueChange}
      disabled={disabled}
      className="w-[22rem] max-w-full"
    >
      {RESET_MODE_OPTIONS.map((option) => (
        <label key={option.value} className={styles.card()}>
          <RadioGroupItem value={option.value} appearance="none" />
          <span className="grid min-w-0 gap-1">
            <span className={styles.label()}>{option.label}</span>
            <span className="text-xs leading-5 text-foreground-muted">{option.description}</span>
          </span>
        </label>
      ))}
    </RadioGroup>
  );
}
```

## States

- **Chosen:** an ink dot, not a yellow fill. A chosen value is ink, as a checked checkbox is, so radios and checkboxes agree and yellow stays for "you are here" ([States](https://raft-design.botiverse.build/foundations/states.md)).
- **Disabled:** the shared disabled look, for the circle and the label.

## Behavior

- Make each answer a row at least 44px high, and make the whole row clickable. In its row the circle's hit area reaches 4px past its line, half the 8px gap between answers, so a tap between two lands on the nearer; the label is the target for the rest.
- Always have one answer chosen. Choose the most common and safest answer by default.

## Content

- Set each answer's label in `text-subhead` at weight 600, and its description in `text-footnote` and `foreground-muted`.

## Accessibility

RadioGroup wraps Base UI RadioGroup, and `RadioGroupItem` wraps Base UI Radio. The group is one tab stop; the arrow keys move between answers and choose them. Each item's hidden native input drives its checked, disabled and focus state, and the focus ring is drawn on the circle ([States](https://raft-design.botiverse.build/foundations/states.md#focus)).

- Name the group: `aria-label`, or `aria-labelledby` pointing at the visible question (Notify me about).
- Name each answer with the `<label>` around it.

## API

### RadioGroup

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultValue` | `T` |  | The uncontrolled value of the radio button that should be initially selected. |
| `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` | `((instance: HTMLInputElement \| null) => void \| (() => void \| { [UNDEFINED_VOID_ONLY]: never; }) \| DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) \| RefObject<HTMLInputElement \| null>` |  | A ref to access the hidden input element. |
| `name` | `string` |  | Identifies the field when a form is submitted. |
| `onValueChange` | `(value: T, eventDetails: BaseRadioGroup.ChangeEventDetails) => 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 \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |
| `required` | `boolean` | `false` | Whether the user must choose a value before submitting a form. |
| `value` | `T` |  | The controlled value of the radio item that should be currently selected. |

Also takes the attributes of its element.

### RadioGroupIndicator

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `keepMounted` | `boolean` | `false` | Whether to keep the HTML element in the DOM when the radio button is inactive. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### RadioGroupItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `appearance` | `"none" \| "default"` | `"default"` |  |
| `disabled` | `boolean` |  | Whether the component should ignore user interaction. |
| `inputRef` | `((instance: HTMLInputElement \| null) => void \| (() => void \| { [UNDEFINED_VOID_ONLY]: never; }) \| DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) \| RefObject<HTMLInputElement \| null>` |  | A ref to access the hidden input element. |
| `nativeButton` | `boolean` | `false` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| `readOnly` | `boolean` |  | Whether the user should be unable to select the radio button. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |
| `required` | `boolean` |  | Whether the user must choose a value before submitting a form. |
| `value` (required) | `T` |  | The unique identifying value of the radio in a group. |

Also takes the attributes of its element.
