{
  "title": "Radio",
  "description": "Single-selection group with full keyboard navigation and roving tabindex.",
  "url": "/docs/components/radio",
  "since": "0.2.0",
  "tags": [
    "control",
    "form"
  ],
  "platform": "both",
  "source": "---\ntitle: Radio\ndescription: Single-selection group with full keyboard navigation and roving tabindex.\nsince: 0.2.0\ntags: [control, form]\nplatform: both\ncategory: controls\n---\n\nimport { BundleSize } from '@/components/bundle-size';\nimport { Preview } from '@/components/preview';\nimport { PropsTable } from '@/components/props-table';\n\n<BundleSize component=\"Radio\" />\n\n## At a glance\n\n- `<Radio.Group>` owns the selected value (controlled via `value` + `onChange`, or uncontrolled via `defaultValue`).\n- `<Radio>` declares a single option — it must be rendered inside a `<Radio.Group>` or you'll get a runtime error explaining why.\n- Keyboard nav follows the WAI-ARIA radiogroup pattern: arrow keys move between options (selection follows focus), `Home` / `End` jump to first / last, wrap-around at the edges.\n- Roving tabindex: tabbing into the group lands on the selected option; only one option participates in the tab order at a time.\n\n## Preview\n\n<Preview name=\"radio-basic\" />\n\n## `<Radio.Group>` `value` and `defaultValue`\n\nControlled vs. uncontrolled selection. The Group fires `onChange(next)`\nwhenever the user moves through the options.\n\n```tsx\n// Controlled\nconst [plan, setPlan] = useState<'free' | 'pro' | 'team'>('free');\n<Radio.Group value={plan} onChange={setPlan} aria-label=\"Plan\">\n    <Radio value=\"free\" label=\"Free\" />\n    <Radio value=\"pro\" label=\"Pro\" />\n    <Radio value=\"team\" label=\"Team\" />\n</Radio.Group>\n\n// Uncontrolled\n<Radio.Group defaultValue=\"free\" aria-label=\"Plan\">{/* … */}</Radio.Group>\n```\n\n## `<Radio.Group>` `disabled`\n\nDisables every option in the group. Individual options can also opt\nout with their own `disabled`.\n\n```tsx\n<Radio.Group disabled value=\"pro\">{/* … */}</Radio.Group>\n```\n\n## `<Radio>` `label`\n\nRenders a clickable label next to the dot. Pressing the label is the\nsame target as pressing the dot. For richer label content (a price\ntag, helper text), pass `children` instead — the children slot\nreplaces the auto-rendered label.\n\n```tsx\n<Radio value=\"pro\" label=\"Pro — $12 / month\" />\n<Radio value=\"team\">\n    <YourCustomPlanRow />\n</Radio>\n```\n\n## `<Radio>` `disabled`\n\nGreys an individual option out and skips it during arrow-key\nnavigation.\n\n```tsx\n<Radio value=\"enterprise\" label=\"Enterprise\" disabled />\n```\n\n## Naming the group\n\nAlways name the group via `aria-label`, `aria-labelledby`, or a visible\nheading wrapped in `aria-labelledby`. An un-named radiogroup is hard\nfor assistive tech to announce as a unit.\n\n```tsx\n<>\n    <Text variant=\"body-sm\" id=\"plan-heading\">Choose a plan</Text>\n    <Radio.Group aria-labelledby=\"plan-heading\" defaultValue=\"free\">{/* … */}</Radio.Group>\n</>\n```\n\n## Grouped with Field.Group\n\nFor label + description + error around a radio set, use `<Field.Group>`:\n\n```tsx\nimport { useState } from 'react';\nimport { Field, Radio } from '@nori-ui/core';\n\nexport const Example = () => {\n    const [value, setValue] = useState<string | null>(null);\n    return (\n        <Field.Group label=\"Plan\" description=\"Pick the tier that fits your team.\" required>\n            <Radio.Group value={value} onChange={setValue}>\n                <Radio value=\"hobby\" label=\"Hobby\" />\n                <Radio value=\"pro\" label=\"Pro\" />\n                <Radio value=\"enterprise\" label=\"Enterprise\" />\n            </Radio.Group>\n        </Field.Group>\n    );\n};\n```\n\nSee [Field](/docs/components/field) for the full API.\n\n## Props\n\n### `<Radio.Group>`\n\n<PropsTable component=\"RadioGroup\" />\n\n### `<Radio>`\n\n<PropsTable component=\"Radio\" />\n"
}
