Segmented Control

A single-select group of segments for switching between a small, fixed set of views or options.

Usage

A row of segments where exactly one is selected. A thumb slides to the active segment. Use it for a small, fixed set of mutually exclusive options that fit on one line, such as a chart period or a view mode.

The control fills its container and the segments share the width equally. Put it in a width-constrained wrapper to make it narrower.

import {
  SegmentedControl,
  SegmentedControlItem,
} from '@epilot/spark-ui/segmented-control'
function PeriodPicker() {
  return (
    <SegmentedControl
      aria-label="Period"
      value={period}
      onValueChange={setPeriod}
    >
      <SegmentedControlItem value="day">Day</SegmentedControlItem>
      <SegmentedControlItem value="week">Week</SegmentedControlItem>
      <SegmentedControlItem value="month">Month</SegmentedControlItem>
    </SegmentedControl>
  )
}

Reach for a segmented control when the options are few (two to five) and worth keeping visible at all times. For switching between panels of page content use Tabs. For a longer list, richer option cards, or options that wrap, use Radio Card Group. For a single on and off state use Switch. This component replaces the deprecated pill variant of Tabs.

Examples

Styles

styleVariant sets both the track chrome and the selected segment. solid and soft sit in a filled groove; surface adds a bordered, faintly-filled track; outline is a bare outline. The selected segment fills with the accent for solid, and is a raised neutral surface for the rest (differing by border).

Sizes

size sets the height and label typography. Use small in toolbars and dense layouts, medium for prominent, touch-first controls.

With icons

Put a MaterialSymbol glyph before the label inside the segment. It inherits the segment's text color (so it turns with the active state) and scales with size. Use the bare glyph here, not the boxed Icon display component. An icon-only segment needs an aria-label so it still has an accessible name.

Disabled segment

Set disabled on a single segment to block it while the rest stay usable. The segment keeps its place and grays out.

API Reference

SegmentedControl

Renders Base UI Tabs as a value selector (no content panels). The selected segment reads the shared --spark-style-* tokens, so its look tracks the theme like the other controls.

PropTypeDefaultDescription
valuestringControlled selected value. Provide onValueChange to update it.
defaultValuestringInitial selected value when uncontrolled. Set one so a segment is active on mount.
onValueChange(value: string, details) => voidFires when the selection changes.
styleVariant"solid" | "soft" | "surface" | "outline""solid"Sets the track chrome (filled groove, bordered surface, or bare outline) and the selected segment (accent fill for solid, raised neutral surface otherwise).
size"small" | "default" | "medium""default"Control height, padding, and label typography. Matches the Button size scale.
aria-labelstringNames the group. Lands on the role="tablist" track, not the outer element.
classNamestringMerges onto the track (the visible container).

SegmentedControlItem

One segment. Renders a Base UI Tabs.Tab.

PropTypeDefaultDescription
valuestringRequired. The value this segment selects.
disabledbooleanfalseBlocks this segment while the others stay usable.
classNamestringMerges onto the segment.

Accessibility

  • The track is a role="tablist" and each segment a role="tab", with the active segment marked aria-selected. Always pass aria-label or aria-labelledby so the group has a name.
  • Arrow keys move between segments and Home and End jump to the first and last. Activation is manual: the focused segment is selected on Enter or Space, or on click.
  • A disabled segment stays focusable and surfaces aria-disabled so assistive tech can announce it.
  • The thumb slide is suppressed under prefers-reduced-motion.
  • Give an icon-only segment an aria-label; a segment with a visible label needs nothing extra.

On this page