Segmented control
Switches between a few views of one thing.
Sizes
sm
defaultdefault
lg
Multiple
falsedefault
true
Count
countdefault
States
rest
hover
held
focus-visible
chosen
disabled
Usage
When to use it
- Use
- Two to five views of the same content: all, errors, warnings; hour, day, week.
- Use
- A filter where any number can be on at once (multiple).
- Not for
- A setting saved with a form, where each choice needs a sentence. Use Radio group.
- Not for
- A single on or off. Use Switch.
- Not for
- Actions side by side, nothing chosen. Use Button group.
Rules
7 rules
- Must
- Pass value or defaultValue to a single control. Because nothing is chosen until the first press, and a radio can never go back to none, so the view below has no state to show.
- Must
- Name the control with aria-label or aria-labelledby. Because a screen reader announces "radio group" with no question, then the bare segment names.
- Must
- Put a number in count, never in the label. Because count is tabular, reserves its width and is read as a description, so the name stays "Errors" while it ticks and the segments never move.
- Never
- Give the control or a segment a fixed width; let the labels size it, or pass fill. Because a fixed width clips a longer label or a translation, and the track is meant to be as wide as its words.
- Never
- Show the chosen segment with the slab recipe or a status colour. Because the slab is the one action on the screen and a chosen view is not an action; the checked recipe draws it in yellow as a state, with an ink label and no halo, which is how Sodium shows a value chosen.
- Should
- Keep it to two to five segments with labels of one or two words. Because a sixth segment or a long label squeezes every segment, and a label that ends in an ellipsis hides the view's name.
- Should
- Match size to the buttons in the same row: sm in a panel head of sm buttons.
State order
6 states, highest first
- 1
- disabled
- 2
- chosen
- 3
- held
- 4
- focus-visible
- 5
- hover
- 6
- rest
Accessibility
5 checks
- Single is a radiogroup with one tab stop; each segment is a radio. Multiple is a group of toggle buttons with aria-pressed.
- The count is aria-hidden and reaches the segment as its description.
- The chosen segment is yellow with a gold edge and an ink label (12.8:1) in light and the yellow tint with a yellow edge and label (9.0:1) in dark; the others are text-2 on the off-white body, 7.3:1 (9.9:1 in dark).
- A label cut by an ellipsis keeps its full text as the title and the name.
- Focus is the 2px --focus ring around the segment, raised over its neighbours.
Keyboard
3 keys
- Arrow keys
- Single: move to the next segment and choose it. Multiple: move focus only.
- Space
- Chooses the focused segment, or turns it on or off with multiple.
- Tab
- Enters on the chosen segment and leaves the control.
Edge cases
6 edge cases
- Count goes from 99 to 100
- The slot was reserved for the widest value seen, or countMax, so nothing moves.
- Longer label
- The segment grows with it; nothing clips. With fill the segments share the width and a label too long ends in an ellipsis.
- Disabled segment
- Arrow keys skip it; its label turns the disabled colour.
- Right to left
- Segments run right to left and the arrows follow.
- Changing the choice
- The fills crossfade on --dur-light and the new segment's light rises; nothing slides.
- Forced colors
- Segments draw ButtonText edges; the chosen one takes Highlight, unlit.
API
6 props
multiplebooleandefaultfalsevalue, defaultValue, onValueChangestring | string[]size'sm' | 'default' | 'lg'default'default'fillbooleandefaultfalsecountMaxnumberSegmentedControlItem value, count, countMax, disabledstring, number, number, boolean
Tokens
24 tokens
--check-off- var(--secondary-bg)
--check-off-hover- #ffffff
--well- #f4f3ee
--line- #e2dfd5
--text-1- #161512
--text-2- #55524a
--text-disabled- #a8a499
--check-bg- #ffd400
--check-edge- #d9b400
--check-mark- #161512
--primary-light- rgb(255 255 255 / 0.55)
--primary-light-hover- rgb(255 255 255 / 0.85)
--dur-light- 240ms
--dur-glow- 320ms
--ease-soft- cubic-bezier(0.22, 0.8, 0.24, 1)
--control-sm- 26px
--control- 32px
--control-lg- 36px
--z-raised- 10
--type-label- 400 13px/16px var(--font-mono)
--type-label-sm- 400 12px/16px var(--font-mono)
--type-label-lg- 400 14px/20px var(--font-mono)
--focus- #7a5d00
--dur-state- 120ms