You're in the Library

SegmentedControl

Picks one of a few views or modes, shown side by side as attached buttons: the brand of a preview, the density, light or dark. The choice applies at once, on the current screen.
Stable
Edit

Preview appears after the next Storybook update.

SegmentedControl example
<SegmentedControl />
Rendered with the real componentFrom the code
Light
Dark

When to use

Use it for

  • Switching how the same content is shown: Light or Dark, Comfortable or Compact, Grid or List.
  • Short options where seeing every option at once helps the choice: two to five usually, more for filters people switch between often (status filters). On a phone the track scrolls sideways and never widens the page.
  • Appearance strong when the control is what the view is about (the status filter of a list): the selected segment fills with Deep Harbor. At most one strong control per view.
  • The change applies immediately, with no Save button.
  • Size sm in dense toolbars, menus and panels; md everywhere else, including a page's filter bar, where it matches the md search field and Select beside it. Heights follow the density setting.
  • fullWidth inside a narrow panel or menu, so the segments share the width equally.

Not for

  • Switching between panels of different content: use Tabs, which link each tab to its panel.
  • A setting in a form that is saved later: use RadioGroup, which shows a label and help per option.
  • Long labels, or options people pick once and forget: use Select.
  • Turning one thing on or off: use Switch.
  • Triggering actions (Export, Delete): use Button or an attached ButtonGroup.

Do and don't

Every example is rendered with the real component, so it always matches what ships.

Do

Short, parallel labels

One or two words per segment, same grammatical form, so the set reads as one choice.

Preview brand
Do

Label outside the track

Put a visible label to the left of the track, as a caption on its center line, and name the group with it (aria-labelledby, or the same words in label). The track holds the options and nothing else.

Do

Strong for the control the view is about

A list filtered by status: the selected filter fills with Deep Harbor, the strongest signal on the screen. Six short filters are fine; on a phone they scroll sideways.

Do

Always start with a selection

A segmented control shows the current mode. Pass value or defaultValue so one segment is always selected.

Don't

Actions dressed as segments

Segments select a mode; they never run a command. "Export" and "Share" belong on buttons.

Don't

The label inside the track

Never put the group's label in the same pill as the options. Inside one container it reads as one more option ("Brand" next to "Bridge" and "Ocean"). Keep it outside, to the left.

Don't

Too many or too long options

Two or three words per segment make the track longer than the screen and stop reading as one control. Keep labels to one or two words, or use Select.

Accessibility

  • Base UI RadioGroup with native button radios: role radiogroup on the row, role radio and aria-checked on each segment.
  • Tab enters the group on the selected segment and leaves it; arrow keys move the selection, Home and End jump to the ends.
  • Give the group a name: aria-labelledby pointing at the visible label outside the track, or label (aria-label) when the context already names it.
  • The selected segment is marked by a raised plate with a checked-color edge (or the Deep Harbor fill with strong) and a darker text color, not by color alone.
  • When the track scrolls, arrow keys still move the selection and the selected segment scrolls into view.
  • The focus ring (focus.ring.color) shows on keyboard focus only.
  • Rendered without JavaScript, the HTML keeps role, aria-checked and data-value, so a small script can drive it: flip aria-checked and tabindex, and handle arrow keys.

Content guidelines

  • Sentence case nouns or adjectives: "Compact", "Dark", "Grid". No verbs: segments are states, not actions.
  • One or two words. Drop the shared word: "Light, Dark", not "Light theme, Dark theme".
  • Name the group with what is being chosen: "Density", "Brand", "Themes shown". When it changes only a preview, say so: "Preview brand".
  • A visible label is a caption to the left of the track (caption size, muted text color), never a segment inside it.

From the code Generated from the component, not editable here.

Anatomy and tokens

Read from packages/react/src/components/SegmentedControl/SegmentedControl.module.css at build time. Every value is a token: open one to see its alias chain in both themes.

30 tokens16 semantic or component, change with theme and brand2 density, change with comfortable or compact12 from the shared scales

Motion and interaction

SegmentedControl animates on hover and press. Try it in the preview at the top.

WhenWhat changesDurationEasing
HoverText colormotion.duration.instant75 msmotion.easing.standardsettles in place
PressBackgroundmotion.duration.instant75 msmotion.easing.standardsettles in place

With reduced motion turned on, every change happens at once.

From the code Generated from the component, not editable here.

Props

Parsed from the TypeScript types in packages/react/src/components/SegmentedControl/SegmentedControl.tsx, so this table cannot drift from the code. It also accepts every native <div> attribute except children, defaultValue, onChange, aria-label, dir.

PropTypeDefaultDescription
itemsrequiredSegmentedControlItem[]noneThe segments, two or more. An array (not children) so the control also renders from Astro and other static templates. When they do not fit (six filters on a phone), the track scrolls sideways inside its container and never widens the page; the selected segment is scrolled into view.
labelstringnoneAccessible name of the group. Required unless you pass `aria-labelledby` pointing at a visible label.
valuestringnoneNo description in the code yet.
defaultValuestringnoneNo description in the code yet.
onValueChange(value: string) => voidnoneNo description in the code yet.
size'sm' | 'md''md'Default `md`. Use `sm` in toolbars and dense panels. Heights follow the density tokens.
appearance'default' | 'strong''default'Default `default`. `strong` fills the selected segment with Deep Harbor: one per view at most.
fullWidthbooleanfalseStretch to the container; segments share the width equally.
disabledbooleannoneNo description in the code yet.
namestringnoneForm field name: the selected value is submitted with the form.

Comes with markSegmentOverflow, each accepting native <div> attributes.

Playground

Change the props and see the real SegmentedControl update. Controls are generated from the TypeScript props; nothing here changes the system.

Code for these props
<SegmentedControl label="Density" defaultValue="comfortable" />

size

appearance

Workbench

For contributors: every state with the accessibility panel, in the component workbench (Storybook). The stories double as visual test fixtures; the guidance on this page is the user documentation (open SegmentedControl in Storybook).

Embed Storybook stories here (10)

Loads from https://bridge-storybook.floriangouloubi.com (set STORYBOOK_URL at build time). If Storybook is not running, the frames stay empty.

Default

Small

WithIcons

ThreeOptions

FullWidth

Disabled

Strong

ManyOnPhone

WithVisibleLabel

AllStates

From the code Generated from the component, not editable here.

Changelog

  • New

    SegmentedControl: pick one of two to five views or modes in a row of attached buttons (Base UI RadioGroup with native button radios)

    v1.0.0, Nov 11, 2026

  • Fix

    Target sizes: SegmentedControl sm segments are at least the small control height (32px), and standalone Links are at least 24px tall (WCAG 2.5.8)

    v1.0.0, Nov 11, 2026

  • Fix

    SegmentedControl docs: a visible label goes outside the track, to its left, and names the group with aria-labelledby

    v1.0.0, Nov 11, 2026

All release notes