Skip to content

Documentation

neelam-ui

Search documentation

Getting Started
Forms
Overlays
Navigation
Data Display
Layout
AI & Chat
Blocks
GitHub repository

Radio Group

A set of mutually exclusive options. Real <input type="radio"> elements sharing a name, so both exclusivity and arrow-key navigation come from the browser.

Plan

Usage#

import { RadioGroup, RadioGroupItem } from "neelam-ui";
 
<RadioGroup defaultValue="pro" aria-labelledby="plan-label">
  <label>
    <RadioGroupItem value="starter" />
    Starter
  </label>
  <label>
    <RadioGroupItem value="pro" />
    Pro
  </label>
</RadioGroup>

Labelling the group#

RadioGroup renders <div role="radiogroup">, which carries no name of its own. Point aria-labelledby at the visible heading, or pass aria-label if there isn't one — what a radio group represents is specific to each use, so it is not something the component could sensibly hardcode.

An unnamed radiogroup announces nothing

Without aria-label or aria-labelledby, a screen reader reads out each option but never says what the choice is about. This is the single most common mistake with this component.

Individual options are labelled the ordinary way — an <label> wrapping the item and its text. There is no bundled per-item label, for the same reason Checkbox has none.

The shared name#

Every RadioGroupItem inherits one name from the group, generated with useId unless you pass your own. That single attribute is what makes the browser treat them as one group — selecting one clears the rest, and the arrow keys move between them — with no key handling of our own.

Pass name explicitly when the group is inside a real <form> and you need a stable field name in the submitted data.

Controlled#

const [plan, setPlan] = useState("pro");
 
<RadioGroup value={plan} onValueChange={setPlan} aria-label="Plan">…</RadioGroup>

disabled on the group disables every option; an individual RadioGroupItem can still set its own.

Keyboard#

Keyboard shortcuts
KeyBehaviour
TabMoves focus into the group — to the selected option, or the first one if nothing is selected.
Selects the next option, wrapping at the end.
Selects the previous option, wrapping at the start.
SpaceSelects the focused option, when the group had nothing selected.

All of it is native radio-input behaviour from the shared name — none of these keys are intercepted.

Accessibility#

  • The group is role="radiogroup"; each option is a native <input type="radio">, so role, selection, and form participation come from the platform.
  • The whole group is a single tab stop. Tab enters and leaves it; the arrow keys move within. This is the roving-focus behaviour the APG specifies, and here it is the browser's own.
  • The selected dot is aria-hidden — the input's checked property already carries the state.
  • Selection follows focus, as it does for native radios. If choosing an option triggers something expensive, debounce the effect rather than changing the interaction.

API reference#

RadioGroup#

Props for RadioGroup
PropTypeDefault
defaultValuestring
disabled

Disables every item in the group. An individual `RadioGroupItem` can still override this itself.

boolean
name

Shared across every `RadioGroupItem` so the browser's own radio-group behavior (mutual exclusivity, arrow-key navigation between them) applies. Omit to have one generated.

string
onValueChange((value: string) => void)
valuestring

RadioGroupItem#

Props for RadioGroupItem
PropTypeDefault
defaultValuestring
disabled

Disables every item in the group. An individual `RadioGroupItem` can still override this itself.

boolean
name

Shared across every `RadioGroupItem` so the browser's own radio-group behavior (mutual exclusivity, arrow-key navigation between them) applies. Omit to have one generated.

string
onValueChange((value: string) => void)
valuestring