# RadioGroup

> Accessible radio button group with legend and validation; with startLabel and endLabel it is a rating scale.

- Status: beta
- Group: form
- Page: https://www.digitaltableteur.com/design-system/components/radio-group
- Contract: https://www.digitaltableteur.com/contracts/v1/RadioGroup.contract.json

## When to use

RadioGroup is the standard way to ask for an exclusive choice: it renders a fieldset legend, the labelled radios, helper and error text, and manages the selection in controlled or uncontrolled mode.

## When not to use

- Do not use radios for actions or navigation; picking an option must not immediately commit anything.

## Look-alikes

- CheckboxGroup: Do not use it for exclusive choices; that is RadioGroup.
- Select: Use Select for five or more exclusive options, or when space is tight; RadioGroup is better when all options should stay visible.
- SelectableCard: For bare text options without supporting content, use RadioGroup or CheckboxGroup.
- SelectableCardGroup: Do not use rich cards when plain RadioGroup or CheckboxGroup labels communicate the choices clearly.

## Props

| Prop | Type | Default | Description | Required |
| --- | --- | --- | --- | --- |
| `name` | `string` | none | Native form field name shared by the set; auto-generated when omitted. | No |
| `legend` | `string` | none | Group label rendered as the fieldset legend. | Yes |
| `options` | `RadioGroupOption[]` | none | Radio options; per-option disabled keeps the choice visible. | Yes |
| `value` | `string` | none | Controlled selected value; "" is treated as absent (uncontrolled) | No |
| `defaultValue` | `string` | none | Initial value when uncontrolled. | No |
| `onValueChange` | `(value: string) => void` | none | Fired with the next value when selection changes. | No |
| `orientation` | `"horizontal" \| "vertical"` | `vertical` | Stack direction. | No |
| `size` | `"sm" \| "md" \| "lg" \| "S" \| "M" \| "L"` | `md` | Radio control size. | No |
| `disabled` | `boolean` | `false` | Disables the whole set. | No |
| `error` | `string` | none | Error message; announces via role=alert and sets aria-invalid. | No |
| `helperText` | `string` | none | Helper copy below the group; always rendered, alongside error when both are set. | No |
| `startLabel` | `string` | none | Anchor shown before the first option, for rating scales ("Serious" in Serious 1 to 5 Playful). Announced paired with its option, not on its own. | No |
| `endLabel` | `string` | none | Anchor shown after the last option; pairs with startLabel. | No |
| `hideLegend` | `boolean` | `false` | Keeps the legend as the accessible name but hides it visually. | No |
| `className` | `string` | none | Merged onto the fieldset. | No |

## Variants

- Available orientations: `vertical`, `horizontal` (default `vertical`). Forwarded to fieldset layout class in RadioGroup.module.css.
- Available sizes: `sm`, `md`, `lg`, `S`, `M`, `L` (default `sm`).

## Invalid combinations

- MUST NOT: `value` and `defaultValue` set together. `value` makes the component controlled and `defaultValue` uncontrolled; pass one, not both.

## Keyboard

The contract records no keyboard interaction.

## Rules

Every item under When not to use is a MUST NOT. MUST and SHOULD follow RFC 2119.

| Level | Rule | How it is checked |
| --- | --- | --- |
| MUST NOT | `value` and `defaultValue` set together. `value` makes the component controlled and `defaultValue` uncontrolled; pass one, not both. | Checked by the MCP tool validate_component_usage. |
| MUST | Required stories produce no axe violations at the configured severity. | Automated by `npm run test:stories:matrix:ci`. WCAG 2.5.8. |
| MUST | The accessibility tree (role, name, state) matches the committed snapshot for every required story. | Automated by `npm run test:stories:matrix:ci`. WCAG 1.3.1, 4.1.2. |
| MUST | The component remains legible and operable under a real browser forced-colors: active pass. | Automated by `npm run test:stories:hc`. |
| MUST | A human completed an end-to-end accessibility review of this component. | Manual review, last on 2026-05-28. |
| SHOULD | Write the legend as the question (Delivery speed); the options then read as answers. | Not stated in the contract. |
| SHOULD | Use it whenever all options should be visible at once; switch to Select when space is tight or options exceed about seven. | Not stated in the contract. |
| SHOULD | Preselect the safest common option with defaultValue when a recommendation exists. | Not stated in the contract. |
| SHOULD | Use orientation=horizontal only for a few short options that still fit one line on mobile. | Not stated in the contract. |
| SHOULD | For a rating scale, give numbered options with startLabel and endLabel (Serious 1 to 5 Playful) and hideLegend when the anchors already say what is rated; the legend stays the accessible name. | Not stated in the contract. |
| SHOULD NOT | Do not leave a required group unselected without a recovery plan; users cannot un-pick a radio once one is chosen. | Not stated in the contract. |
| MUST NOT | Maintainers: Do not bypass design tokens or skip forced-colors verification at beta. | Release review; not machine-checked. |

## Install and import

- Install: `npm install @digitaltableteur/react` (version 0.9.1, restricted access on npm).
- Import: `import { RadioGroup } from "@digitaltableteur/react";`
- Entry point: `@digitaltableteur/react/forms` exports it too.
- Styles: import `@digitaltableteur/react/style.css` once at the app boundary.
- Also available as the custom element `<dt-radio-group>`; see the custom elements manifest.

## Links

- Machine-readable contract (Design System Contract 1.0): https://www.digitaltableteur.com/contracts/v1/RadioGroup.contract.json
- Markdown version of this page: https://www.digitaltableteur.com/design-system/components/radio-group.md
- Component index as JSON: https://www.digitaltableteur.com/design-system/components.json
- All components: https://www.digitaltableteur.com/design-system/components
- Source: The implementation lives in a private repository. This page, its Markdown twin and the contract are generated from it at build time and are the public source of truth.
