JGuang

SegmentedControl

SegmentedControl · Astryx

Preview
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0

SegmentedControl

A segmented button group that allows users to make a single selection from a small set of mutually exclusive options. Use SegmentedControl when all options should be visible at once and the selection controls a value or mode, not page navigation.

Installation

npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0

Requires React 19+, base CSS and a Theme provider. See Setup.

Segmented Control

A segmented button group that allows users to make a single selection from a small set of mutually exclusive options. Use SegmentedControl when all options should be visible at once and the selection controls a value or mode, not page navigation.

PropTypeDefaultDescription
value *stringThe currently selected value (controlled).
onChange *(value: string) => voidCallback fired when a segment is selected.
label *stringAccessible label for the radio group (used as aria-label, never rendered visually).
size'sm' | 'md' | 'lg''md'Size variant for the control.
layout'hug' | 'fill''hug'Layout mode. hug (default) sizes segments to content, capped at the container width (segment labels truncate when it is too narrow); fill stretches them equally to fill the container.
isDisabledbooleanfalseWhether the entire control is disabled.
disabledMessagestringExplains why the control is disabled. Applies to the whole-group disabled state (isDisabled), not per segment. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the control focusable via aria-disabled (selection stays blocked). Use this instead of wrapping a disabled SegmentedControl in Tooltip. Disabled controls swallow the hover events an external Tooltip needs.
children *ReactNodeSegmentedControlItem children.
xstyleStyleXStylesStyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value: not an inline style object like style={{}}.

Usage guidance

  • Use for switching between 2–5 mutually exclusive views or modes where all options should be visible.
  • Provide a descriptive label for the control to ensure the group is accessible to screen readers.
  • Avoid: Use for page-level navigation; use TabList instead. TabList is a navigation component, while SegmentedControl is an input that always has exactly one selected option.
  • Avoid: Use for simple on/off states; use ToggleButton instead. ToggleButton can be toggled on or off independently, while SegmentedControl enforces a single selection from a group.
  • Avoid: Wrap a disabled SegmentedControl in Tooltip to explain why it is disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.

In a narrow card

import {useState} from 'react';
import {Card} from '@astryxdesign/core/Card';
import {Heading} from '@astryxdesign/core/Heading';
import {
  SegmentedControl,
  SegmentedControlItem,
} from '@astryxdesign/core/SegmentedControl';
import {Text} from '@astryxdesign/core/Text';
import {VStack} from '@astryxdesign/core/VStack';

const SUMMARY = {
  overview: '4 projects, 2 due this week.',
  activity: '18 updates since Monday.',
  members: '12 members, 3 pending invites.',
  billing: 'Next invoice on October 1.',
};

// Inside a VStack the default hug layout keeps its content width; it does not
// stretch to the card. When the card is narrower than the control (a 320px
// phone column), the control caps at the card width and each label truncates
// with an ellipsis instead of running past the card edge. The full label stays
// each segment's accessible name. Keep labels short for phone widths.
function WorkspaceCard() {
  const [view, setView] = useState('overview');
  return (
    <Card maxWidth={480}>
      <VStack gap={3}>
        <Heading level={3}>Team workspace</Heading>
        <SegmentedControl label="Workspace view" value={view} onChange={setView}>
          <SegmentedControlItem value="overview" label="Overview" />
          <SegmentedControlItem value="activity" label="Activity" />
          <SegmentedControlItem value="members" label="Members" />
          <SegmentedControlItem value="billing" label="Billing" />
        </SegmentedControl>
        <Text color="secondary">{SUMMARY[view]}</Text>
      </VStack>
    </Card>
  );
}

Theming API

{
  "targets": [
    {
      "className": "astryx-segmented-control",
      "visualProps": [
        "size"
      ]
    },
    {
      "className": "astryx-segmented-control-item",
      "visualProps": [
        "size"
      ],
      "states": [
        "selected",
        "disabled"
      ]
    }
  ],
  "vars": [
    {
      "name": "--_segmented-control-radius",
      "description": "Border radius of the segmented control",
      "default": "var(--radius-element)",
      "private": true
    },
    {
      "name": "--_segmented-control-padding",
      "description": "Inner padding of the segmented control",
      "default": "var(--spacing-0-5)",
      "private": true
    }
  ],
  "derived": [
    {
      "property": "borderRadius",
      "vars": [
        "--_segmented-control-radius"
      ]
    },
    {
      "property": "padding",
      "vars": [
        "--_segmented-control-padding"
      ]
    }
  ]
}

SegmentedControlItem

Accessibility

  • Text label: Each label must have at least 4.5:1 contrast with its segment background. Check unselected, Hover, Pointer down, and selected colors as they appear on screen. For Hover and Pointer down, measure the final background after the overlay is applied. — 4.5:1
  • Essential icon: When a segment has no visible label, its icon must have at least 3:1 contrast with the segment background. An icon beside a visible label does not need its own check. — 3:1
  • Selected state indicator: The selected background must reach 3:1 only when users need it to tell selected from unselected. Label color and weight also show selection. — 3:1 if relied upon
  • Visible control boundary: The control edge or segment borders need at least 3:1 contrast when users need them to see the choices. — 3:1 if needed
  • Keyboard focus indicator: The focus outline must have at least 3:1 contrast with the area around the segment. Check it on the track and selected background. — 3:1
  • Disabled appearance: Disabled controls do not need to meet these contrast ratios. — Not required

Anatomy

  • Control: Container for the mutually exclusive segment choices.
  • Segment: Individual choice within the control.
  • Label: Visible text identifying a segment when its label is not hidden.
  • Icon: Optional caller-supplied icon shown inside a segment.

Segmented Control Item

Individual segment item rendering as a radio button within the segmented control.

PropTypeDefaultDescription
value *stringUnique value for this segment, matched against the parent value.
label *stringAccessible label for this segment. Rendered as visible text unless isLabelHidden is true.
isLabelHiddenbooleanfalseWhether the label is visually hidden. When true, only the icon is displayed and label is used as aria-label.
iconReactNodeIcon element displayed before the label.
isDisabledbooleanfalseWhether this individual item is disabled.

Examples

  • Segmented Control Item: Segmented control items with text labels and icons, including a disabled item.
  • Segmented Control:
  • SegmentedControl — Disabled Item: Segmented control with an individually disabled option for unavailable choices.
  • SegmentedControl — Fill Layout: Segmented control that stretches segments equally to fill the available width, useful for fixed-width containers.
  • SegmentedControl — Icon Only: Compact segmented control with hidden labels, showing only icons for space-constrained layouts.
  • SegmentedControlItem — Basic: Label-only options inside a SegmentedControl. Each item declares a value; the parent control holds the selected value and change handler.
  • SegmentedControl — With Icons: Segmented control with icon and label pairs for a view mode switcher.