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
| Prop | Default | Description |
|---|---|---|
value | _ | stringThe 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) => voidCalled with the next value whenever the selection changes, keyboard included. |
aria-label? | _ | stringAccessible name for the group. Required unless aria-labelledby is given — there is no visible label or legend to fall back on. |
aria-labelledby? | _ | stringId of an existing element that names the group. An alternative to aria-label, not an addition. |
children? | _ | ReactNodeThe 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? | _ | stringUnique 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.
| Prop | Default | Description |
|---|---|---|
value | _ | stringThe value this segment selects. Must be unique within the group. |
children? | _ | ReactNodeThe segment's visible label. |
isDisabled? | false | booleanPrevents selection. Renders aria-disabled rather than the native disabled attribute, so the segment stays reachable by assistive technology. |
onClick? | _ | (event: React.MouseEvent<HTMLButtonElement>) => voidCalled after the group's own selection handling. |
onFocus? / onBlur? | _ | (event: React.FocusEvent<HTMLButtonElement>) => voidCalled after the group's own focus handling. |
id? | _ | stringUnique 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.
| Prop | Default | Description |
|---|---|---|
value | _ | stringThe value this segment selects. Must be unique within the group. |
icon | _ | (props: IconProps) => React.ReactNodeThe icon to render. Sized from the group's size — 16px at small and standard, 20px at large. |
aria-label | _ | stringDescribes the segment to assistive technology. Required. |
isDisabled? | false | booleanPrevents selection. Renders aria-disabled rather than the native disabled attribute. |
onClick? | _ | (event: React.MouseEvent<HTMLButtonElement>) => voidCalled after the group's own selection handling. |
id? | _ | stringUnique 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.