Components · Forms
SegmentedControl
Single choice as a radio group with arrow keys; ToggleGroup for several on/off choices.
View switch
import { useState } from 'react';
import { SegmentedControl } from '@rookcom/monarch-react';
// BoardIcon, GridIcon, ListIcon: demo icons from the docs site; use your own.
function ViewSwitch() {
const [view, setView] = useState<'list' | 'board' | 'grid'>('list');
return (
<SegmentedControl
aria-label="View"
value={view}
onValueChange={setView}
options={[
{ value: 'list', label: 'List', icon: <ListIcon /> },
{ value: 'board', label: 'Board', icon: <BoardIcon /> },
{ value: 'grid', label: 'Grid', icon: <GridIcon /> },
]}
/>
);
}
<ViewSwitch />Usage examples
Sizes, icon-only, suffix
import { SegmentedControl } from '@rookcom/monarch-react';
// GridIcon, ListIcon: demo icons from the docs site; use your own.
<SegmentedControl
aria-label="Layout"
size="sm"
defaultValue="grid"
options={[
{ value: 'list', label: 'List', icon: <ListIcon />, hideLabel: true },
{ value: 'grid', label: 'Grid', icon: <GridIcon />, hideLabel: true },
]}
/>
<SegmentedControl
aria-label="Billing period"
size="lg"
defaultValue="monthly"
options={[
{ value: 'monthly', label: 'Monthly' },
{ value: 'yearly', label: 'Yearly', suffix: '2 months free' },
]}
/>
<SegmentedControl
aria-label="Range"
block
defaultValue="7d"
options={[
{ value: '1d', label: '1 day' },
{ value: '7d', label: '7 days' },
{ value: '30d', label: '30 days' },
{ value: '90d', label: '90 days', disabled: true },
]}
/>ToggleGroup with counts
import { useState } from 'react';
import { ToggleGroup } from '@rookcom/monarch-react';
function Filters() {
const [value, setValue] = useState<string[]>(['open']);
return (
<ToggleGroup
aria-label="Show"
size="sm"
value={value}
onValueChange={setValue}
options={[
{ value: 'open', label: 'Open', count: 12 },
{ value: 'waiting', label: 'Waiting', count: 3 },
{ value: 'closed', label: 'Closed', count: 48 },
]}
/>
);
}
<Filters />Purpose
Picks one of a few short, mutually exclusive options shown side by side: a view (list or board), a period (monthly or yearly), a scope. ToggleGroup is the same control for several independent on/off options.
Use when
- Two to five short options, all visible at once, and changing them updates the view immediately.
- A compact filter set where several options can be on (ToggleGroup).
Avoid when
- Each option shows its own panel of content: use Tabs.
- More than five options, or long labels: use Select.
- A single on/off setting: use Switch.
Design principles
- One ARIA model per meaning: single choice is always a radio group, multiple choice is always toggle buttons. Never a tablist without panels.
- One Tab stop for the whole control; arrow keys move inside it.
- The selected option is a raised surface with a border, so selection is not shown by shade alone.
- Labels stay short; icons help but never replace the accessible name.
API reference
import { SegmentedControl, ToggleGroup } from '@rookcom/monarch-react';| Export | Kind | Rendering | Root class |
|---|---|---|---|
SegmentedControl | component | client | .mn-segmented-control |
ToggleGroup | component | client | .mn-segmented-control |
Both components take an options array of SegmentedOption and forward their ref to the group <div>.
Both are generic over the option value: value and onValueChange are typed by the options you pass.
An accessible name is required by the type: aria-label or aria-labelledby.
Generated from the TypeScript declarations of packages/react/src/components/forms/segmented-control.
Properties
SegmentedControl: value/onValueChange(value) (controlled) or defaultValue; name adds a hidden input for forms.
ToggleGroup: value/onValueChange(values) (controlled) or defaultValue, as arrays in option order.
SegmentedOption: value, label, icon, hideLabel (icon-only), count, suffix, disabled.
SegmentedControl
| Prop | Type | Description |
|---|---|---|
options required | readonly SegmentedOption<T>[] | |
block | boolean | Stretch to the container and share the width equally. |
children | ReactNode | |
className | string | |
defaultValue | T | |
dir | "ltr" | "rtl" | Text direction. Left and Right arrows follow it. Default: inherited from the page. |
disabled | boolean | Disable every option. |
id | string | |
name | string | Submits the selected value with a form under this name. |
onValueChange | ((value: T) => void) | |
ref | Ref<HTMLDivElement> | |
required | boolean | Requires a selection (sets aria-required). |
size | SegmentedControlSize | |
value | T |
ToggleGroup
| Prop | Type | Description |
|---|---|---|
options required | readonly SegmentedOption<T>[] | |
block | boolean | Stretch to the container and share the width equally. |
children | ReactNode | |
className | string | |
defaultValue | readonly T[] | |
dir | "ltr" | "rtl" | Text direction. Left and Right arrows follow it. Default: inherited from the page. |
disabled | boolean | Disable every option. |
id | string | |
onValueChange | ((value: T[]) => void) | |
ref | Ref<HTMLDivElement> | |
size | SegmentedControlSize | |
value | readonly T[] |
Variants
| Prop | Values | Description |
|---|---|---|
size | 'sm''md''lg' | Control height from --mn-control-*. Default md. |
block | 'true''false' | Fill the container with equal-width options (data-block). |
option.hideLabel | 'true''false' | Icon-only option; the label stays the accessible name and the tooltip. |
States
| State | Exposed as | Behaviour |
|---|---|---|
| Selected / pressed | aria-checked="true" or aria-pressed="true", data-state="on" | Raised surface with a border and shadow, so the state is not shown by colour alone. |
| Hover | – | Text darkens to the default text colour. |
| Focus | – | Focus ring on :focus-visible. |
| Disabled option or group | disabled, data-disabled, aria-disabled | Skipped by arrow keys; the whole group can be disabled. |
Interaction behaviour
- SegmentedControl: clicking an option selects it. Moving with the arrow keys also selects (selection follows focus, as in a native radio group).
- ToggleGroup: clicking or pressing Space/Enter toggles an option; arrow keys only move focus.
- Arrow keys wrap around and skip disabled options. Left and Right follow the text direction.
- With
name, the selected value is submitted with the surrounding form.
Keyboard support
| Keys | Action |
|---|---|
| Tab | Enters the control at the selected option (or the first enabled one), then leaves it. ToggleGroup returns to the last focused option. |
| → / ↓ | Next option (SegmentedControl: and select it). |
| ← / ↑ | Previous option (SegmentedControl: and select it). |
| Home / End | First / last enabled option. |
| Space | Select (SegmentedControl) or toggle (ToggleGroup) the focused option. |
| Enter | ToggleGroup: toggle the focused option. |
Accessibility
- Role
- radiogroup with radio items (SegmentedControl); group with toggle buttons (ToggleGroup)
- Accessible name
aria-labeloraria-labelledbyon the group (required); each option is named by its label, plus its count.
- Follows the WAI-ARIA APG radio group and toolbar-style roving tabindex patterns.
- Icon-only options keep the label as visually hidden text and as a tooltip.
- Counts are separated by a space in the accessible name: "Open 12".
- On touch devices options are at least 44px tall.
Responsive behaviour
- Never wider than its container: if the options do not fit, the track scrolls horizontally and the focused option scrolls into view.
blockshares the full width equally, for example in a phone-width dialog.- Taller hit areas under
pointer: coarse.
Theme support
- Track, surface, border and text are Monarch tokens, so every theme and more-contrast mode applies.
- Transitions are removed under
prefers-reduced-motion: reduce; forced-colours mode outlines the selected option.
Do and don't
Do
- Keep labels to one or two words and use the same size for all options.
- Name the group ("View", "Billing period") with
aria-labelor a visible heading andaria-labelledby. - Use ToggleGroup when options are independent, SegmentedControl when they are exclusive.
Don't
- Do not use it as tabs: when each option shows a panel of content, use Tabs.
- Do not mix icon-only and labelled options in one control.
- Do not trigger slow or destructive work on selection; arrow keys select as they move.
Related components
Dependencies
| Kind | Depends on |
|---|---|
| npm packages | react |
| Monarch components | None |
Testing coverage
- Labelled radio group with one checked radio.
- One Tab stop on the selected option, or the first enabled one.
- Arrow keys move and select, wrap, skip disabled options; Home/End; Up/Down.
- Left and Right reverse in RTL.
- Selects on click and Space; controlled mode follows the value prop only.
- Icon-only options and counts in the accessible name; form submission through
name; disabled group. - ToggleGroup: labelled group of toggle buttons; multi-select in option order; arrows move without toggling; Space and Enter toggle; remembers the Tab stop; controlled value.
| Check | Covered |
|---|---|
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery; Cross-browser axe: /components/segmented-control |
Tests: packages/react/src/components/forms/segmented-control