Thumbprint logo

Components

Segmented Control

Mutually exclusive options presented as equal-width segments

SegmentedControl lets users switch between a small set of closely related views or content subsets within the same context — for example toggling between “List”, “Grid”, and “Map” views, or refining a list with filters like “All”, “Active”, and “Archived”. Options are mutually exclusive and exactly one segment is always selected; selecting a new segment automatically deselects the previous one, and the selection cannot be cleared. It is built on React Aria and styled with Thumbprint v2 semantic tokens, so it supports dark mode automatically.

Use a maximum of three segments with short, parallel, text-only labels. If you need more options, iconography, or navigation across pages, reach for Tabs, Dropdown, or Chip instead.

Basic segmented control

Provide value and onChange for a controlled component. Each Segment reports its value when selected. The control fills its container’s width and every segment receives equal width, with the selected pill sliding horizontally to the new segment.

function SegmentedControlExample() {
    const [value, setValue] = React.useState('list');

    return (
        <SegmentedControl value={value} onChange={setValue} accessibilityLabel="View">
            <Segment value="list">List</Segment>
            <Segment value="grid">Grid</Segment>
            <Segment value="map">Map</Segment>
        </SegmentedControl>
    );
}

Uncontrolled usage

For an uncontrolled component, set the initial selection with defaultValue. When neither value nor defaultValue is provided, the first segment is selected, since a segmented control always has a selection.

<SegmentedControl defaultValue="edit" accessibilityLabel="Mode">
    <Segment value="view">View</Segment>
    <Segment value="edit">Edit</Segment>
    <Segment value="preview">Preview</Segment>
</SegmentedControl>

Sizes

The size prop switches between the large (default, 48px) and small (40px) controls. Choose based on the surrounding UI density. The large control fills its container’s width, while the small control hugs its content; segments receive equal width in both sizes.

<SegmentedControl size="small" defaultValue="all" accessibilityLabel="Filter">
    <Segment value="all">All</Segment>
    <Segment value="active">Active</Segment>
    <Segment value="archived">Archived</Segment>
</SegmentedControl>

Narrow screens

Labels are never truncated. Segments share the container’s width equally while everything fits, but a segment never shrinks below its label’s width — on narrow viewports such as mobile web, the control instead scrolls horizontally (swipe on touch devices) and the selected segment is kept in view. The example below uses deliberately long labels: resize the browser window (or open this page on a phone) to see the full labels preserved and the control become scrollable. Keep real labels to one or two short, parallel words so scrolling is rarely needed; if the labels can’t be shortened, reconsider the options or use Tabs or Dropdown instead.

function SegmentedControlExample() {
    const [value, setValue] = React.useState('recommendations');

    return (
        <SegmentedControl value={value} onChange={setValue} accessibilityLabel="Section">
            <Segment value="recommendations">Recommendations for you</Segment>
            <Segment value="availability">Availability calendar</Segment>
            <Segment value="messages">Unread messages</Segment>
        </SegmentedControl>
    );
}

Disabled segments

Set isDisabled on a Segment to disable that segment. Disabled segments are visually subdued and cannot be selected.

<SegmentedControl defaultValue="list" accessibilityLabel="View">
    <Segment value="list">List</Segment>
    <Segment value="grid" isDisabled>
        Grid
    </Segment>
    <Segment value="map">Map</Segment>
</SegmentedControl>

Accessibility

  • The control renders as a radiogroup with one radio per segment, conveying the single-selection pattern to screen readers. Label the group with accessibilityLabel.
  • Keyboard support follows the React Aria toolbar pattern: arrow keys move focus between segments, and Space or Enter selects the focused segment.
  • A visible focus indicator appears when navigating with a keyboard, and the sliding selection animation is disabled for users with prefers-reduced-motion enabled.

The full props for SegmentedControl and Segment are listed in the tables below.

Props

Segment

An individual segment within a `SegmentedControl`, composed as a direct child.
  • value
    required

    The value reported to the group's onChange when this segment is selected. Must be unique within the group.

    Type
    string
  • children
    required

    The segment's text label. Keep labels short (1–2 words); segments are text-only.

    Type
    string
  • isDisabled

    Disables this individual segment. Maps to React Aria's isDisabled.

    Type
    boolean
  • accessibilityLabel

    Accessible label for the segment, for when the visible text label isn't descriptive enough on its own. Maps to React Aria's aria-label.

    Type
    string
  • dataTestId

    A selector hook into the React component for use in automated testing environments.

    Type
    string

SegmentedControl

  • children
    required

    Two or three Segment options. Segments must be direct children (not wrapped in fragments or intermediate components).

    Type
    React.ReactNode
  • value

    The selected segment's value. Provide together with onChange for a controlled control.

    Type
    string
  • defaultValue

    The initially selected value for an uncontrolled control. Defaults to the first segment's value, since a segmented control always has exactly one selected segment.

    Type
    string
  • onChange

    Function that runs when the selected segment changes. It receives the newly selected segment's value. Not called when the already-selected segment is pressed again — the selection cannot be cleared.

    Type
    (value: string) => void
  • size

    Sets the size of the control. 'large' (the default) is the 48px-tall control that fills its container's width; 'small' is the 40px-tall control that hugs its content. Maps to the [data-size] attribute.

    Type
    'small' | 'large'
    Default
    'large'
  • accessibilityLabel

    Accessible label for the group. Maps to React Aria's aria-label.

    Type
    string
  • dataTestId

    A selector hook into the React component for use in automated testing environments. Applied to the group's root element.

    Type
    string