SegmentedGroup

SegmentedGroup lets someone pick one of a few mutually exclusive options and switches what they are looking at the moment they pick it — a view, a data representation, a chart axis. The selection is persistent, can start empty, and there is no Save step.

Reach for it instead of radio buttons, a dropdown, or in-context tabs when the choice changes what is displayed. If the buttons trigger actions rather than holding a selection, use ButtonGroup.

Segments are composed as children: SegmentedGroupButton for text and SegmentedGroupIconButton for icons. Which component you use is the segment type, so an icon segment cannot be missing its icon and a text segment cannot carry one.

Quick Start

Installation
npm install @adaptavant/eds-core
Import
import { SegmentedGroup } from '@adaptavant/eds-core';

Basic Usage​

Compose text segments with SegmentedGroupButton. The group is controlled — value and onChange are required, and at most one segment is selected.

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

return (
  <SegmentedGroup aria-label="View mode" onChange={setValue} value={value}>
    <SegmentedGroupButton value="chart">Chart</SegmentedGroupButton>
    <SegmentedGroupButton value="list">List</SegmentedGroupButton>
    <SegmentedGroupButton value="table">Table</SegmentedGroupButton>
  </SegmentedGroup>
);

Icon Segments​

Use SegmentedGroupIconButton for icon-only segments. Both icon and aria-label are required, so a segment can never reach screen readers unnamed. Pair each one with a Tooltip so sighted users get the label too.

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

return (
  <SegmentedGroup aria-label="View mode" onChange={setValue} value={value}>
    <SegmentedGroupIconButton
      aria-label="Chart"
      icon={ChartBarIcon}
      value="chart"
    />
    <SegmentedGroupIconButton
      aria-label="List"
      icon={ListCheckmarkIcon}
      value="list"
    />
    <SegmentedGroupIconButton
      aria-label="Calendar"
      icon={CalendarFoldIcon}
      value="calendar"
    />
  </SegmentedGroup>
);

Sizes​

size is set once on the group and every segment follows it. The track height matches the design scale at 26, 32 and 40px.

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

return (
  <Box className="flex flex-col items-start gap-4">
    {['small', 'standard', 'large'].map((size) => (
      <SegmentedGroup
        aria-label={`View mode (${size})`}
        key={size}
        onChange={setValue}
        size={size}
        value={value}
      >
        <SegmentedGroupButton value="chart">Chart</SegmentedGroupButton>
        <SegmentedGroupButton value="list">List</SegmentedGroupButton>
        <SegmentedGroupButton value="table">Table</SegmentedGroupButton>
      </SegmentedGroup>
    ))}
  </Box>
);

Vertical​

Vertical groups are icon-only by design. A SegmentedGroupButton inside one throws in development, so the constraint surfaces immediately rather than as a broken layout.

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

return (
  <SegmentedGroup
    aria-label="View mode"
    onChange={setValue}
    orientation="vertical"
    value={value}
  >
    <SegmentedGroupIconButton
      aria-label="Chart"
      icon={ChartBarIcon}
      value="chart"
    />
    <SegmentedGroupIconButton
      aria-label="List"
      icon={ListCheckmarkIcon}
      value="list"
    />
    <SegmentedGroupIconButton
      aria-label="Calendar"
      icon={CalendarFoldIcon}
      value="calendar"
    />
  </SegmentedGroup>
);

API Reference​

SegmentedGroup

SegmentedGroup​

PropDefaultDescription
value_string
The value of the currently selected segment. The component is controlled. An empty string means nothing is selected yet; every enabled segment is then a tab stop, and a segment is selected only by click or arrow key, never by focus alone.
onChange_(value: string) => void
Called with the next value whenever the selection changes, keyboard included.
aria-label?_string
Accessible name for the group. Required unless aria-labelledby is given — there is no visible label or legend to fall back on.
aria-labelledby?_string
Id of an existing element that names the group. An alternative to aria-label, not an addition.
children?_ReactNode
The segments. Only SegmentedGroupButton and SegmentedGroupIconButton may be direct children.
size?standard'small' | 'standard' | 'large'
Size applied to every segment. Button's xLarge is deliberately excluded; design specified three sizes.
orientation?horizontal'horizontal' | 'vertical'
Layout axis, reported as aria-orientation. All four arrow keys move between segments on either axis, per the WAI-ARIA radiogroup pattern; only the horizontal pair mirrors under dir="rtl". Vertical groups are icon-only by design — a SegmentedGroupButton inside one throws in development.
id?_string
Unique identifier for the group root element.

SegmentedGroupButton

SegmentedGroupButton​

A text segment. size is owned by the group, and there is deliberately no isPressed — selection is group-owned and expressed as aria-checked, never aria-pressed.

PropDefaultDescription
value_string
The value this segment selects. Must be unique within the group.
children?_ReactNode
The segment's visible label.
isDisabled?falseboolean
Prevents selection. Renders aria-disabled rather than the native disabled attribute, so the segment stays reachable by assistive technology.
onClick?_(event: React.MouseEvent<HTMLButtonElement>) => void
Called after the group's own selection handling.
onFocus? / onBlur?_(event: React.FocusEvent<HTMLButtonElement>) => void
Called after the group's own focus handling.
id?_string
Unique identifier for the segment.

SegmentedGroupIconButton

SegmentedGroupIconButton​

An icon-only segment. Both icon and aria-label are required — an icon segment with no accessible name will not compile. Pair it with a Tooltip so sighted users get the label too; EDS does not wrap triggers automatically.

PropDefaultDescription
value_string
The value this segment selects. Must be unique within the group.
icon_(props: IconProps) => React.ReactNode
The icon to render. Sized from the group's size — 16px at small and standard, 20px at large.
aria-label_string
Describes the segment to assistive technology. Required.
isDisabled?falseboolean
Prevents selection. Renders aria-disabled rather than the native disabled attribute.
onClick?_(event: React.MouseEvent<HTMLButtonElement>) => void
Called after the group's own selection handling.
id?_string
Unique identifier for the segment.

Style API​

Our design system components include style props that allow you to easily customize different parts of each component to match your design needs.

Please refer to the Style API documentation for more insights.

SegmentedGroup parts​

SegmentedGroup exposes two stylable parts. root is the track — fill, border, radius and padding. item is applied to every segment, so a group-level classNames.item styles them all; a className on an individual segment wins over it.

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

return (
  <SegmentedGroup
    aria-label="View mode"
    classNames={{ item: 'font-stronger', root: 'border-primary' }}
    onChange={setValue}
    value={value}
  >
    <SegmentedGroupButton value="chart">Chart</SegmentedGroupButton>
    <SegmentedGroupButton value="list">List</SegmentedGroupButton>
  </SegmentedGroup>
);

Usage guidelines​

Do

Use SegmentedGroup when clicking a segment changes what the user is looking at — a view, a data representation, a chart axis. Keep the set small, around two to five options, and give every group an accessible name.

Don’t

Don't use SegmentedGroup to trigger actions — that is ButtonGroup. Don't use it for an optional choice that people can clear; an empty value is only a starting state, and once a segment is selected there is no way back to none. And don't reach for it when the options need descriptions or more than a short label, where RadioGroup reads better.