Skip to content
Cloaked Design SystemControls

SegmentedControl

A list of buttons for switching one view mode, with the selected option marked by aria-current.

Implementation preview
COMPLETE STORYBOOK REFERENCE

Every example. In one place.

Explore every named example, including states, compositions, and theme variants.

4 stories
SELECTED EXAMPLE

Playground

Open controls
Playground selectedOpen canvas
Example sourcepackages/design-system/src/components/controls/SegmentedControl/SegmentedControl.stories.tsxExport Playground

Implementation preview Local Storybook build 0.2.0-preview.1. Source specification status does not approve this runtime.

Stories + source JSON ↗

Local specification source status: preview. Implementation preview pending sign-off.

A list of buttons for switching one view mode, with the selected option marked by aria-current.

Design-owned component contract ↗Version 0.2.0-preview.1 · Implementation preview pending sign-off
Implementation API 1 entry point · source-derived preview

Read the actual callable API and preserve its composition constraints. Each union branch is a complete alternative; props marked forbidden cannot be supplied.

import { SegmentedControl } from '@cloakedinc/design-system';
SegmentedControlSegmentedControlProps

packages/design-system/src/components/controls/SegmentedControl/SegmentedControl.tsx:63

PropTypePresenceNotes
itemsSegmentedControlItem[]RequiredOptions rendered in order. Keep the set to 5 or fewer, per the iOS guideline.
activeIdstringRequiredControlled `id` of the active option. An id matching no item leaves none active.
onSelect((id: string) => void) | undefinedOptionalCalled with the selected option id. Skipped when the pressed option is already active.
ariaLabelstring | undefinedOptionalAccessible name for the strip, announced before the options — such as `Time range`.
classNamestring | undefinedOptional
hapticHapticFeedback | undefinedOptionalHaptic feedback for selection changes. Defaults to `true` (`selection`).Source default: true
sizingSegmentedControlSizing | undefinedOptionalHow options claim width. `fill` (default) shares the strip equally and ellipsises labels that do not fit; `hug` sizes each option to its own label, for a strip that should be no wider than its content.Source default: 'fill'
testIdstring | undefinedOptionalOptional test selector. Each option derives `${testId}-${id}`.

Only explicit destructured parameter initializers are extracted. Other runtime defaults remain in implementation source.

Overview

An iOS-style single-select switch for up to 5 mutually exclusive modes that re-filter one view in place, such as a time range or a chart granularity.

It is a list of buttons

The markup is a <ul> of buttons, and the selected one carries aria-current. It is deliberately none of the roles it resembles:

  • Not a radiogroup. That role needs radio semantics on every option — role="radio" with aria-checked — plus radio-group arrow-key behavior. These are ordinary buttons.
  • Not a tablist. That role promises content panels and arrow-key traversal.
  • Not a toolbar. That role is a collection of function buttons.

Each of those roles carries its own keyboard and announcement rules, so borrowing the wrong one misleads anyone using assistive technology.

When to use

Reach for SegmentedControl when a small, fixed set of options changes how the same content is sliced and all options fit inline. Choosing between the neighbours:

  • The options switch between distinct content panels rather than re-filtering one view? Use Tabs.
  • The set grows from data, wraps, or scrolls? Use a ChipGroup. The boundary is the shape of the set: a segmented control is fixed, connected, and equal-width.
  • A binary input, such as on and off? Use Toggle.
  • You want a labelled form field with descriptions, or more than 5 options? Use RadioGroup.
  • Too many options to sit inline? Use Dropdown + Menu.

Counts and dots

An option may carry a trailing count, a decorative dot, or its own disabled state. A count is text, so it is announced with the label. A dot is not, so put the reason it is there into the label when it carries meaning.

Component specification

$schema../../../specs/component-spec.schema.json
Parts
Id
strip
Label
Strip
Description
The list that owns the accessible name and shares width equally between options.
Required
true
Id
option
Label
Option
Description
One button per option. Carries aria-current and its own disabled state.
Required
true
Id
count
Label
Count
Description
Optional trailing number, rendered as text so it is announced with the label.
Id
dot
Label
Dot
Description
Optional decorative attention indicator.
Allowed Design Choices
Variants
Pill
Label
Pill
Description
Equal-width pill segments with filled active and outlined inactive treatments.
Sizes
Variants
Id
pill
Label
Pill
Description
Compact mode switch with one active option.
Platforms
  • ios
  • android
  • web
SizesNone specified
Measurements
Id
option-height
Label
Option height
Kind
exact
Token
--cds-size-32
Value
32
Unit
px
Applies To
  • option
Id
option-horizontal-padding
Label
Option horizontal padding
Kind
exact
Token
--cds-space-16
Value
16
Unit
px
Applies To
  • option
Id
option-content-gap
Label
Option content gap
Kind
exact
Token
--cds-space-4
Value
4
Unit
px
Applies To
  • option
  • count
  • dot
Id
option-gap
Label
Gap between options
Kind
exact
Token
--cds-space-8
Value
8
Unit
px
Applies To
  • strip
Id
dot-size
Label
Dot size
Kind
exact
Token
--cds-size-8
Value
8
Unit
px
Applies To
  • dot
Layout
Alignment
Options share equal width and center their content in both axes.
Flow
Two to four options form one horizontal row.
Notes
  • Use Tabs when options switch distinct content panels rather than one view mode.
  • Use a ChipGroup when the set grows from data, wraps, or scrolls.
  • Use Toggle for a binary input rather than a two-option strip.
Typography And Color
Typography
Label
Segment label
Description
Use the compact link ramp for concise mode labels.
Token
--cds-type-link-size
Colors
Label
Active segment
Description
Use the bold semantic surface.
Token
--cds-bg-bold
Label
Active label
Description
Use text intended for bold surfaces.
Token
--cds-text-on-bold
Label
Inactive label
Description
Use primary text emphasis.
Token
--cds-text-primary
Label
Inactive border
Description
Use the subtle surface stroke.
Token
--cds-surface-subtle
States
Id
active
Label
Active
Description
Bold fill with on-bold text, marked by aria-current.
Id
inactive
Label
Inactive
Description
Transparent fill with a subtle border.
Id
disabled
Label
Disabled
Description
One option dimmed and blocked through the native disabled attribute.
Id
with-count
Label
With count
Description
Shows a trailing number after the label.
Id
with-dot
Label
With dot
Description
Shows a trailing attention indicator.
Behavior
Label
Select on press
Description
Invoke onSelect with the pressed option id. Skip it when that option is already active.
Label
Per-option disable
Description
An option may be disabled on its own through the native attribute, without disabling the set.
Label
Bounded set
Description
Keep the set to 5 or fewer options, per the iOS guideline. Past that, reach for a Chip group or a Select.
Accessibility
Label
Not a radiogroup
Description
A radiogroup needs radio semantics on every option, role=radio with aria-checked, plus radio-group arrow-key behavior. These are ordinary buttons, so the selected one carries aria-current.
Label
Not a tablist
Description
A tablist promises content panels and arrow-key traversal. Reach for Tabs when the options switch panels.
Label
Explicit list role
Description
The list keeps role=list because removing the bullets strips list semantics in some engines.
Label
Group name
Description
Pass ariaLabel so the set is announced before its options.
Label
Counts over dots
Description
A count is text and announces. A dot is decorative, so say why it is there in the label when it carries meaning.
Content Adaptation
Label
Option count
Description
Keep the set to two through four mutually exclusive modes.
Label
Concise labels
Description
Use parallel, short labels that remain legible at equal widths.
Label
Width sizing
Description
sizing='fill' (default) shares the strip equally and ellipsises a label that does not fit. sizing='hug' sizes each option to its own label, so an auto-width strip is never narrower than its content; use it when the strip sits beside other content rather than filling a row.
Motion Haptics
Motion
Label
Selection treatment
Description
Swap active and inactive color treatments without moving or resizing segments.
Haptics
Label
Selection feedback
Description
Request the selection haptic after the active segment changes.
Platform Differences
Platforms
  • web
Description
Web renders no focus ring and no arrow-key traversal between options.
Rationale
The consumers are a touch iOS WKWebView and a harness previewing it, and per Primer arrow keys must not move selection in a segmented control anyway.
Implementation preview

This design-owned implementation is ready to try and review. It does not establish product approval. Imported specifications retain their original review status.

Inspect version 0.2.0-preview.1