Designer view
Segmented Control
A row (or column) of connected buttons that lets the user switch between mutually-exclusive views of the same content — list vs grid, day vs week vs month, KB vs MB vs GB unit toggles. Distinct from Tabs (which switches between different *content*), distinct from RadioGroup (which captures form values rather than view state), distinct from Toggle (which is binary, not n-way). Used for content-presentation toggles where the underlying data is the same and the segments select the visual shape.
When to use
Use
For switching between mutually-exclusive views of the same content — list vs grid view, day vs week vs month calendar, KB vs MB vs GB unit toggle, light vs dark vs auto theme. The user picks one; the underlying data stays the same and the segment selects how it is presented.
Avoid
For switching between different content panels — that is `Tabs`. For form values with form submission — that is `RadioGroup` (which is semantically identical but visually distinct as separate radio buttons). For binary on/off — that is `Switch` or `Toggle`. For more than 5 options — that is a `Select` dropdown.
Versus related
- tabs
`Tabs` switches between different *content* panels; `SegmentedControl` switches between *view-of-same- content*. The semantic distinction drives the role (`tablist` vs `radiogroup`) and the keyboard model (Tabs may use manual activation; SegmentedControl always uses auto-activation per APG radiogroup).
- radio
`RadioGroup` is the semantic foundation — SegmentedControl IS a radio group, just rendered as connected-segment buttons rather than as separate radio inputs. Use Radio when the choices are part of a form being submitted (gender, plan tier); use SegmentedControl when the choice controls in-page view-presentation.
- toggle
`Toggle` (a.k.a. Switch) is binary on/off; `SegmentedControl` is n-way mutual exclusion. Two- segment SegmentedControls and Toggles can look similar — the distinguisher is whether the option space is binary (Toggle) or could expand (Segmented).
Figma anatomy
| Slot | Figma type | Hint |
|---|---|---|
root | frame | Auto-layout horizontal frame; segments share an outer border |
segment | instance | Segment component instance with selected and unselected variants |
icon | instance | Icon component instance per segment; visibility per variant |
label | text | Segment text style; bound to a component property |
indicator | rectangle | Pill or filled rectangle behind the selected segment; position bound to selection |
Token usage per slot
root- radius
- corner
radius.md
- corner
- color
- background
color.surface.sunken - border
color.border.subtle
- background
segment- spacing
- padding
spacing.compact
- padding
- radius
- corner
radius.sm
- corner
- color
- foreground
color.text.muted - ring
color.border.focus
- foreground
- typography
- size
text.sm - weight
weight.medium
- size
icon- color
- foreground
color.text.muted
- foreground
label- color
- foreground
color.text.muted
- foreground
- typography
- size
text.sm - weight
weight.medium
- size
indicator- radius
- corner
radius.sm
- corner
- color
- background
color.surface.bg
- background
- elevation
- shadow
elevation.sm
- shadow
Figma ↔ Code property map
| Figma | Type | Code | Notes |
|---|---|---|---|
Variant | Variant | variant | Maps standard / icon-only. |
Size | Variant | size | sm / md / lg. |
Orientation | Variant | orientation | horizontal / vertical. Affects keyboard model (ArrowLeft/Right vs ArrowUp/Down). |
Has Indicator | Boolean | hasIndicator | Toggles the sliding-indicator slot. Default true; some variants use only foreground-color contrast for selection without a separate indicator. |
Segment Count | Variant | segments.length | Figma exposes 2/3/4/5 segment counts as a Variant for preview-time layout review. Code accepts an array of segment definitions. |
Selected Segment | Variant | value | Figma exposes "first / second / third selected" as Variant for preview; in code this is a controlled value (the selected segment id). |
Has Icons | Boolean | hasIcons | Toggles per-segment icons. Required for icon-only variant; optional for standard variant. |
Motion
| Transition | Duration token |
|---|---|
indicator | motion.duration.fast |
segmentColorChange | motion.duration.fast |
Responsive behaviour
| Breakpoint | Change |
|---|---|
breakpoint.sm | At and below, the canonical default switches to compact density and may force `orientation="vertical"` when the segment count exceeds 3 (avoids overflow on narrow viewports). For icon-only variant, orientation stays horizontal up to 4 segments because icon-only is space-efficient. |
breakpoint.md | Above this width, orientation and size render as authored. Both orientations are valid; choose per layout context. |
Internationalisation
RTL · mirroring
Horizontal orientation reverses logically — first segment in source order appears at the inline-start (visual right in RTL). ArrowLeft / ArrowRight follow visual direction (per APG radiogroup canonical): ArrowRight moves to the *previous* segment in RTL, ArrowLeft to the *next*. Vertical orientation is direction-neutral (Up/Down keys unchanged). Indicator animation slides along the logical axis.
Text expansion
Segment labels grow with translation. The control sizes to its widest segment (or to fill the container when `width: full`); long-text languages may force wider segments than designed. Avoid fixed segment widths; allow intrinsic-size growth. Above 4 segments with long-text labels, consider switching to vertical orientation or to Tabs.
Variants, properties, states
Variants
Structurally different versions of the component.
standardicon-only Properties
The same component, parameterised.
| Property | Type |
|---|---|
size | sm | md | lg |
orientation | horizontal | vertical |
hasIndicator | boolean |
States
Browser/user-driven (interactive) vs. app-driven (data).
| Kind | States |
|---|---|
interactive | hoverfocus-visibleactivedisabled |
data | selected |
State transitions
| From | To | Trigger |
|---|---|---|
selected | selected | User activates a different segment via Enter / Space or via ArrowKey navigation in roving-tabindex mode (which auto-activates on focus per APG radiogroup contract). The previously-selected segment loses `aria-checked="true"`; the newly-selected segment gains it. The indicator (if present) animates to the new position. |
Figma↔Code mismatches
- 01 Figma
SegmentedControl drawn as Tabs with no visual differentiation
CodeA SegmentedControl with `role="radiogroup"` rather than `role="tablist"`
ConsequenceDesigners may use SegmentedControl visually for what is semantically Tabs (switching between different content panels). Implementations following the design ship `role="radiogroup"` for content-switching, breaking the keyboard model (radiogroup uses ArrowKeys with auto-activation; tablist may use manual activation) and misleading SR users about the surface's purpose.
CorrectDistinguish at the canonical level: SegmentedControl switches view-of-same-content (list vs grid; day vs week vs month); Tabs switches between different content panels. The semantic distinction drives the role (`radiogroup` vs `tablist`). Document the boundary clearly in `whenToUse.vsRelated`.
- 02 Figma
Multi-select segmented control drawn (multiple segments highlighted)
CodeA canonical SegmentedControl is single-select only; multi-select segments are not segmented control
ConsequenceDesigners compose mocks with two or three segments "active" simultaneously. Implementations following the design ship multi-select; the semantic surface is neither radiogroup (single) nor a clean checkbox group (no visible grouping). The control becomes ambiguous.
CorrectSegmentedControl is single-select by canon — `role="radio"` semantics enforce mutual exclusion. For multi-select visual toggles, use a horizontal `<CheckboxGroup>` styled to look segmented; document this as the related pattern, not as a SegmentedControl variant.
- 03 Figma
Selected segment differentiated by a coloured fill alone
CodeSelected segment differentiated by fill PLUS contrast-text PLUS optional border
ConsequenceDesigners may use a single colour change to mark selection. Implementations following the design fail WCAG 1.4.1 (Use of Color) for users with colour-vision deficiencies; SR users hear the same announcement for every segment because the differentiation is purely visual.
CorrectTriple-layer selection differentiation: visual (background + foreground contrast + optional weight or border), accessible state (`aria-checked="true"`), and iconographic (an inline checkmark or selected-state icon for icon-only variants). The `aria-checked` is the source of truth; visuals reinforce.
- 04 Figma
Vertical segmented control drawn but no orientation property documented
CodeA SegmentedControl with an `orientation` property switching between row and column layouts
ConsequenceDesigners compose vertical-segmented variants in mocks without flagging that the orientation is property-driven. Developers ship one orientation; the design is occasionally requested in the other and re-implemented from scratch.
CorrectDocument `orientation` as a first-class property (horizontal default, vertical opt-in). The keyboard model adapts: ArrowLeft / Right for horizontal, ArrowUp / Down for vertical, with Home / End working in both.
Common mistakes
#segmented-as-tabs
Used as content-switching tabs
The control is used to switch between different content panels (a settings page with "Account / Billing / Notifications" sections). Semantically this is Tabs; the `role="radiogroup"` of SegmentedControl is wrong here — SR users hear "radio button" instead of "tab", and panel content does not get the `role="tabpanel"` relationship.
Use Tabs for content-switching. SegmentedControl is for view-of-same-content (list vs grid). The decision test: does activating a segment change *what content is shown* or *how the same content is displayed*? "What" → Tabs; "how" → SegmentedControl.
#segmented-no-roving-tabindex
Every segment has tabindex="0"
Tab cycles through every segment one by one. APG canonical for radiogroup is roving tabindex — only the currently-checked segment has `tabindex="0"`, others have `tabindex="-1"`. ArrowKeys navigate within the group.
Roving tabindex — only the checked segment is in the tab order. ArrowKeys move focus AND selection (per APG radiogroup auto-activation). Tab from the radiogroup moves to the next document focusable, not to the next segment.
#segmented-arrow-no-activate
ArrowKeys move focus but do not activate
Implementation copies the manual-activation pattern from Tabs. ArrowKeys move focus between segments; the user must press Enter / Space to activate. APG radiogroup canonical is auto-activation — ArrowKey IS the activation.
ArrowKey moves focus AND activates the new segment. `aria-checked` flips immediately. The indicator (if present) animates to the new position. Enter / Space are no-ops on the focused segment when it is already checked (or are equivalent to ArrowKey activation if a rare focus-without-check edge case exists).
#segmented-color-only-selected
Selection differentiated by colour alone
Selected segment has a filled background; unselected segments have transparent background. The differentiator is colour. Colour-vision deficient users cannot distinguish; SR users get only the `aria-checked` cue.
Pair colour with non-colour cue: contrast-text foreground change (selected segment uses primary text colour vs muted; unselected uses muted), optional weight change, optional inline checkmark for icon-only-variant. Colour is reinforcement, not the sole signal.
#segmented-overflow-silent
Overflowing segments clipped at the inline-end
Five-segment control rendered in a narrow container; the last two segments clip at the edge. Users cannot see them; SR users navigate to them via ArrowKey but pointer users miss their existence.
For SegmentedControl above 5 segments, redesign as tabs with overflow-scroll, a Select, or splitting into smaller controls. Document the canonical maximum segment count in performance. Above the threshold, SegmentedControl is the wrong pattern.
Accessibility hints
| Slot | Accessibility hint | |
|---|---|---|
root | Apply `role="radiogroup"` plus an `aria-label` (or `aria-labelledby` referencing a heading near the control). The radiogroup is the canonical APG choice because segments are *mutually-exclusive choices about view*, not navigation between content. Tabs would be wrong — Tabs switch content, segments switch view-of- content. | |
segment | Each segment is `role="radio"` with `aria-checked` reflecting selection state. Roving tabindex — only the currently-checked segment has `tabindex="0"`, others have `tabindex="-1"`. Disabled segments use `aria-disabled` and stay focusable so SR users hear them. | |
icon | Decorative when paired with a visible label (`aria-hidden="true"`). For icon-only variant, the segment's `aria-label` carries the meaning ("List view", "Grid view") because the icon alone is not a label. | |
label | Plain text. When icon-only variant is used, the label is hidden visually but kept in the DOM (visually- hidden) for SR users — never replaced by `aria-label` without a visible alternative when the segment also renders text. | |
indicator | Decorative — `aria-hidden="true"`. Selection is communicated through `aria-checked` on the radio segments; the indicator is visual reinforcement. |