A row of two to five segments on an inset track, one of them selected, that picks how one view shows its content, such as a stop's departures by mode.
When to use
Use SegmentedControl to pick how one view shows its content, one choice of two to five, such as a stop's departures by mode.
| Use instead | When |
|---|---|
| Tabs | The choice switches between views of one thing, rather than how one view shows |
| FilterChip | Each choice is a filter that turns on and off on its own |
| RadioGroup | The choice is a form's answer, read down a column |
| Select | There are more than five choices |
| Button | A press runs an action |
| LinkButton | A press goes to another screen |
Kongens Nytorv
Metro M1, M2 and M3.
Variants and states
| Rest | Disabled | |
|---|---|---|
| Default |
- Rest
- Disabled
SegmentedControl styles hover, pressed and focus on its inner parts, which a still preview can't reach, so this table shows rest and disabled only.
Props
| Name | Type | Default | Description |
|---|---|---|---|
value | string | — | The selected segment's value, when the caller controls it. A value no segment has selects none. |
defaultValue | string | — | The segment selected at first, when the control keeps the value itself. Left out, with no value, no segment is selected until one is chosen. |
onChange | (event: Event, value: string, details: ToggleGroup.ChangeEventDetails) => void | — | Called when another segment is selected, by a click, Enter or Space (details.reason 'none'), with that segment's value. Not called when the selected segment is pressed again. event is details.event. |
disabled | boolean | false | Whether every segment ignores the pointer and the keys. The selected segment still shows, and the control leaves the tab order. |
children | ReactNode | — | The control's Segments, in reading order. |
className | string | — | Reaches the root. className is merged after pn-SegmentedControl. |
style | CSSProperties | — | Reaches the root. className is merged after pn-SegmentedControl. |
ref | Ref<HTMLDivElement> | — | Reaches the root. className is merged after pn-SegmentedControl. |
valuestring, default —- The selected segment's
value, when the caller controls it. A value no segment has selects none. defaultValuestring, default —- The segment selected at first, when the control keeps the value itself. Left out, with no
value, no segment is selected until one is chosen. onChange(event: Event, value: string, details: ToggleGroup.ChangeEventDetails) => void, default —- Called when another segment is selected, by a click, Enter or Space (
details.reason'none'), with that segment'svalue. Not called when the selected segment is pressed again.eventisdetails.event. disabledboolean, defaultfalse- Whether every segment ignores the pointer and the keys. The selected segment still shows, and the control leaves the tab order.
childrenReactNode, default —- The control's
Segments, in reading order. classNamestring, default —- Reaches the root.
classNameis merged afterpn-SegmentedControl. styleCSSProperties, default —- Reaches the root.
classNameis merged afterpn-SegmentedControl. refRef<HTMLDivElement>, default —- Reaches the root.
classNameis merged afterpn-SegmentedControl.
Use it
import { SegmentedControl } from '@mylder/perron'Ask your agent
get_reference({ library: "base-ui", component: "ToggleGroup" })get_reference({ library: "base-ui", component: "Toggle" })Keyboard and ARIA
From the Base UI reference for ToggleGroup and Toggle and the installed ToggleGroup, Toggle and the composite root and item they build on. "From" says who does it.
| Key | Does | From |
|---|---|---|
Tab | Moves focus into the control, to its one tab stop: the first enabled segment, or the segment focused last; the next Tab leaves the control | Base UI |
Shift+Tab | Moves focus out of the control, backwards | Base UI |
ArrowRight | Moves focus to the next enabled segment, wrapping from the last to the first while loopFocus is on, without selecting it; the previous one under an rtl DirectionProvider, which Perron doesn't set up | Base UI |
ArrowLeft | Moves focus to the previous enabled segment, wrapping from the first to the last while loopFocus is on, without selecting it; the next one under an rtl DirectionProvider | Base UI |
Home, End | Move focus to the first and the last enabled segment | Base UI |
Enter, Space | Select the focused segment and call onChange; nothing on the selected segment, whose change Perron cancels (CR-4) | The browser's click; Base UI for a non-button render |
ArrowUp and ArrowDown do nothing: the control runs horizontally, and the component adds no keys of its own.
| Element | Role | Accessible name from | States announced |
|---|---|---|---|
root | group, from Base UI | The caller's aria-label or aria-labelledby | none |
segment | button, a native <button>, announced as a toggle by aria-pressed | Its text content: the caller's children | aria-pressed; disabled by the native disabled |
What axe won't catch, from the contract, each with how this component meets it:
- Everything the eye is told, the ear is told, for every shape each prop accepts: the selected segment, which the raised fill and the shadow show, is
aria-pressed="true"and every other segment"false"; a disabled segment's tertiary colour is the nativedisabled, announced as dimmed; a disabled control shows it on each of its segments, which Base UI disables. - Every landmark and named region has an accessible name: the control is a
group, not a landmark, named by the caller'saria-labeloraria-labelledby, which every story sets, so a screen reader says what the choice is about before its segments. - Every default label can be overridden: none here; the control owns no strings.
- Every shape the type accepts renders, in every variant: a segment's
childrenrender a string, an element and a list alike, as one label whose text and elements flow and wrap as one run of text, as theLabelShapesstory shows. - A hidden copy kept for animation is
aria-hidden, and a collapsed region isinert: none here; the control keeps no copy and collapses nothing. - Native semantics first: native
<button>s fromToggle, witharia-pressed, in Base UI'sgroup, with ARIA from the caller only for the group's name. - Focus moves in reading order and returns on close: the arrow keys move through the segments in document order, and Tab leaves the control after its one stop; the control opens and closes nothing, and focus stays on the segment it selects.
- Every target is at least
size.target-minsquare: the smallest target is a segment with a one-letter label,size.segment(32 px) tall and its label plusspace.300on each side (at least 24 px) wide. - Loops stop when the system asks for less motion: none here; the control has no loop and no transition.
Owned strings
No owned strings: every word comes from the caller, as a segment's children or the control's aria-label.
Do and don't
For a caller using the component, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.
| Rule | Level | Do | Don't | Checked by |
|---|---|---|---|---|
| DD-1 | should | Use a SegmentedControl to pick how one view shows its content, such as departures by mode or a chart by day or week | Use it to switch between views of one thing: Tabs change which view shows, a segmented control changes how one view shows | review |
| DD-2 | should | Use it for one choice of two to five | Use it for filters that turn on and off apart, or for more than five choices: filter chips take several, and a long row of segments outgrows a phone | review |
| DD-3 | must | Name the control with aria-label or aria-labelledby, such as "Mode", so a screen reader says what the choice is about | Leave the control unnamed: its segments' words alone don't say what they choose between | review |
| DD-4 | should | Give it a defaultValue or a value, so one segment is always selected | Leave nothing selected without a reason: a control with no raised segment reads as broken | review |
| DD-5 | must | Keep each label to a word or two, in sentence case: "Bus", "Planned works" | Write a sentence in a segment, or rely on a label's colour or an icon alone to say what it chooses | review |
| DD-6 | should | Show a segment whose view is empty, and say so in the view with an EmptyState or a DataStatePanel | Disable a segment because its view has no data: a disabled segment can't say why, and leaves the key order | review |
| DD-7 | must | Run an action with a Button, and group actions in a Toolbar | Use a segment as an action or as navigation: pressing one selects it and keeps it selected, and a link to another screen is a LinkButton | review |