Skip to content

SegmentedControl

<SegmentedControl> is a row of buttons where exactly one is selected. Use for view-axis toggles, period filters, scope switches — anything with two-to-five visible-at-once choices. ViewSwitcher composes this primitive.

import from "@na/ui/components/SegmentedControl" ▶ Open in Storybook packages/ui/src/components/SegmentedControl.tsx

When to use

  • View toggles (List / Grid / Board).
  • Time-period filters (Day / Week / Month / Year).
  • Scope filters (Mine / Team / All).

Don’t use SegmentedControl for: more than 5 options (Select), boolean toggles (Switch), or commands (Button group).

Default

<SegmentedControl
ariaLabel="View"
defaultValue="list"
options={[
{ value: 'list', label: 'List' },
{ value: 'grid', label: 'Grid' },
{ value: 'board', label: 'Board' },
]}
/>

With icons

<SegmentedControl
ariaLabel="View"
defaultValue="list"
options={[
{ value: 'list', label: 'List', icon: IconList },
{ value: 'grid', label: 'Grid', icon: IconLayoutGrid },
{ value: 'board', label: 'Board', icon: IconLayoutKanban },
]}
/>

With counts

<SegmentedControl
ariaLabel="Folder"
defaultValue="inbox"
options={[
{ value: 'inbox', label: 'Inbox', count: 12 },
{ value: 'starred', label: 'Starred', count: 3 },
{ value: 'archive', label: 'Archive', count: 0 },
]}
/>

Small

For inline toolbar density.

<SegmentedControl size="sm" />

Controlled

Active value: list

const [value, setValue] = useState('list');
<SegmentedControl
ariaLabel="View"
value={value}
onValueChange={setValue}
options={[…]}
/>

API

Prop Type Default Description
options * SegmentedControlOption<TValue>[] Two-to-five options. Each: { value, label, icon?, count?, disabled? }.
value TValue Controlled active value. Pair with onValueChange.
defaultValue TValue Initial value for uncontrolled mode.
onValueChange (value: TValue) => void Fires when the user picks a different segment. Doesn't fire when re-clicking the active segment.
ariaLabel * string Describes the choice axis ("View", "Period", "Scope") for screen readers.
size "sm" | "default" "default" Inline-toolbar density vs. form-row density.
disabled boolean false Disable every option.
className string Forwarded to the root.

Design guidelines

✓ Do

  • Keep labels short — one or two words. Long labels stretch the control out of proportion.
  • Order options by frequency of use, left to right. Don't sort alphabetically.
  • Use `size="sm"` inside toolbars; default everywhere else.

✗ Don't

  • Add a 6th option. The pattern caps at five for visual + cognitive reasons; switch to Select.
  • Use SegmentedControl for boolean toggles — that's a Switch.
  • Mix icon-only and label-only segments in the same control. Pick one or the other.

Accessibility

  • Composes Radix ToggleGroup (type="single") — exposes role="radiogroup" with proper radio items.
  • Keyboard: Tab to focus, / to move between items, Enter / Space to select.
  • ariaLabel is required — without it the choice axis has no name.
  • Color is never the only signal — the active state has its own background + shadow.
  • ViewSwitcher — Composes SegmentedControl with a body slot per view.
  • Switch — For boolean toggles.
  • Select — When you outgrow 5 options.

▶ Open SegmentedControl stories in Storybook