SegmentedControl
Radio-style option group with arrow-key navigation and 44px targets.
import { SegmentedControl } from '@elirobinson/react/components/molecules/SegmentedControl';Styles: @elirobinson/react/styles/molecules/SegmentedControl.css — already included when you import @elirobinson/react/styles.css.
Show code
import { useState } from 'react';
import { SegmentedControl } from '@elirobinson/react/components/molecules/SegmentedControl';
const options = [
{ label: 'Day', value: 'day' },
{ label: 'Week', value: 'week' },
{ label: 'Month', value: 'month' },
];
export default function Basic() {
const [value, setValue] = useState('week');
return <SegmentedControl options={options} value={value} onValueChange={setValue} />;
}When to use it
Use SegmentedControl for a small, mutually exclusive set of view or mode options —
Day/Week/Month, List/Grid — where exactly one is always selected and switching is
instant. It's semantically a radio group in a horizontal row, not a set of tabs: Tabs
pairs each option with its own content panel, while SegmentedControl just reports which
option is picked and leaves what happens next to you.
Driving other content
The selected value is ordinary state — use it to switch what renders below the control, the way a view toggle usually works.
- U8 Practice Plans
- U10 Practice Plans
- Season Overview
Show code
import { useState } from 'react';
import { Badge } from '@elirobinson/react/components/atoms/Badge';
import { SegmentedControl } from '@elirobinson/react/components/molecules/SegmentedControl';
const options = [
{ label: 'List', value: 'list' },
{ label: 'Grid', value: 'grid' },
];
const GUIDES = ['U8 Practice Plans', 'U10 Practice Plans', 'Season Overview'];
export default function DrivingContent() {
const [view, setView] = useState('list');
return (
<div className="demo-col">
<SegmentedControl options={options} value={view} onValueChange={setView} />
{view === 'list' ? (
<ul>
{GUIDES.map((guide) => (
<li key={guide}>{guide}</li>
))}
</ul>
) : (
<div className="demo-row">
{GUIDES.map((guide) => (
<Badge key={guide} variant="outline">
{guide}
</Badge>
))}
</div>
)}
</div>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
onValueChangerequired | (value: string) => void | — | — |
optionsrequired | SegmentedControlOption[] | — | — |
valuerequired | string | — | — |
Also accepts all Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'role'> props.
Accessibility
- Renders
role="radiogroup"on the outer element androle="radio"witharia-checkedon each option — the same pattern a native radio group uses, just laid out horizontally. - Uses roving tabindex: only the selected option (or the first one, if none is selected)
is a tab stop; the rest are
tabIndex={-1}.Tabmoves into and out of the whole group in one stop, not option by option. - Keyboard:
ArrowRight/ArrowDownmoves to the next option,ArrowLeft/ArrowUpto the previous, both wrapping at the ends;HomeandEndjump to the first and last option. UnlikeTabs, moving with arrow keys also selects — that's the radio-group convention this component follows. - Clicking an option selects it directly, independent of keyboard focus.
- This is a primary control, so every option keeps the 44×44px touch target minimum.
- The ref forwards to the outer
role="radiogroup"element.
Do
- Keep it to two to five options — past that it stops reading as a single control.
- Use it when exactly one option is always selected; there's no "none selected" state.
- Pair short, parallel labels — Day/Week/Month, not one short label next to one long sentence.
- Let arrow-key movement select immediately, matching native radio-group behavior.
Don't
- Use SegmentedControl when each option should show its own content panel with tab semantics — that's Tabs.
- Use it for a single on/off choice — that's Switch.
- Shrink the touch target below 44px for a denser look; density comes from the visible control, not the hit area.
- Mix it up with RadioGroup for a long vertical list of options — SegmentedControl is for a short horizontal set.