RadioGroup
Context-based radio group; the group owns name, checked state, and change handling.
import { RadioGroup, RadioGroupItem } from '@elirobinson/react/components/atoms/RadioGroup';Styles: @elirobinson/react/styles/molecules/RuleLink.css — already included when you import @elirobinson/react/styles.css.
Show code
import { RadioGroup, RadioGroupItem } from '@elirobinson/react/components/atoms/RadioGroup';
export default function Basic() {
return (
<RadioGroup name="plan" defaultValue="pro">
<RadioGroupItem value="free" label="Free" />
<RadioGroupItem value="pro" label="Pro" />
<RadioGroupItem value="enterprise" label="Enterprise" />
</RadioGroup>
);
}When to use it
Use RadioGroup for a small, always-visible set of mutually exclusive options — two to
six is the sweet spot. Once the list gets long enough to need scrolling or search, reach
for Select or Combobox instead.
RadioGroup is context-based: the group owns name, the current value, and change
handling. RadioGroupItem only takes value and label — it can't take checked,
onChange, or name directly, so there's no way to desync an item from its group.
Rendering a RadioGroupItem outside a RadioGroup throws immediately; the two aren't
meant to be used apart.
Controlled
Pass value and onValueChange to drive selection from your own state. Passing
defaultValue instead makes it uncontrolled — don't pass both.
Booking a 60-minute session.
Show code
import { useState } from 'react';
import { RadioGroup, RadioGroupItem } from '@elirobinson/react/components/atoms/RadioGroup';
export default function Controlled() {
const [duration, setDuration] = useState('60');
return (
<div className="demo-col">
<RadioGroup name="session-length" value={duration} onValueChange={setDuration}>
<RadioGroupItem value="30" label="30 minutes" />
<RadioGroupItem value="60" label="60 minutes" />
<RadioGroupItem value="90" label="90 minutes" />
</RadioGroup>
<p>Booking a {duration}-minute session.</p>
</div>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
namerequired | string | — | — |
defaultValue | string | number | readonly string[] | — | — |
onValueChange | ((value: string) => void) | — | — |
value | string | — | — |
Also accepts all Omit<HTMLAttributes<HTMLDivElement>, 'role'> props.
RadioGroupItem
| Prop | Type | Default | Description |
|---|---|---|---|
labelrequired | string | — | — |
defaultValue | string | number | readonly string[] | — | — |
value | string | number | readonly string[] | — | — |
Also accepts all Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'name' | 'checked' | 'onChange'> props.
Extraction notes: ds-radio-group styles are defined in molecules/RuleLink.css, not a RadioGroup sheet.
Accessibility
- The wrapper renders
role="radiogroup"; eachRadioGroupItemrenders a nativeinput[type="radio"]sharing the group'sname. - Keyboard: because the inputs share a native
name, arrow keys move between items and select as they go, andTabtreats the whole group as a single stop — that's the browser's built-in radio-group behavior, not something this component reimplements. RadioGrouphas nolabelprop of its own — introduce it with a standaloneLabelconnected viaaria-labelledby(a plaindivisn't a labelable element, sohtmlFordoesn't reliably associate here).- The ref on
RadioGroupforwards to the wrapping<div>; the ref onRadioGroupItemforwards to its<input>.
Do
- Give RadioGroup a name and either defaultValue (uncontrolled) or value + onValueChange (controlled) — pick one mode.
- Use it for 2–6 mutually exclusive, always-visible options.
- Pair a standalone Label above the group via aria-labelledby, since RadioGroup has no label prop of its own.
- Give every RadioGroupItem a distinct, real value and label.
Don't
- Try to pass checked, onChange, or name to a RadioGroupItem — the type doesn’t allow it, and the group owns all three.
- Render a RadioGroupItem outside a RadioGroup — it throws immediately, with no fallback behavior.
- Reach for RadioGroup when the option list is long or needs search — use Select or Combobox.
- Pass both value and defaultValue — value wins, and defaultValue is only read on the first render.