Skip to content

Segmented Control

A row of connected buttons where exactly one option is chosen, for switching a view or a setting with two to five choices. Use SegmentedControlItem for each option.

Inputv0.1.0experimentalWCAG 2.2 AAView spec
import { SegmentedControl, SegmentedControlItem } from "@rdloom/react";

export default function SegmentedControlBasicExample() {
  return (
    <SegmentedControl label="View" defaultSelectedKey="list">
      <SegmentedControlItem id="list">List</SegmentedControlItem>
      <SegmentedControlItem id="board">Board</SegmentedControlItem>
      <SegmentedControlItem id="calendar">Calendar</SegmentedControlItem>
    </SegmentedControl>
  );
}

Installation

npx rdloom add segmented-control

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/segmented-control.json

Works, but without upgrade tracking.

Usage

import { SegmentedControl, SegmentedControlItem } from "@rdloom/react";

<SegmentedControl label="View" defaultSelectedKey="list">
  <SegmentedControlItem id="list">List</SegmentedControlItem>
  <SegmentedControlItem id="board">Board</SegmentedControlItem>
  <SegmentedControlItem id="calendar">Calendar</SegmentedControlItem>
</SegmentedControl>

Controlled

$19 a month.

import { useState } from "react";
import { SegmentedControl, SegmentedControlItem } from "@rdloom/react";

export default function SegmentedControlControlledExample() {
  const [billing, setBilling] = useState<string>("monthly");
  return (
    <div className="flex flex-col items-center gap-3 text-sm">
      <SegmentedControl label="Billing period" selectedKey={billing} onChange={(key) => setBilling(String(key))}>
        <SegmentedControlItem id="monthly">Monthly</SegmentedControlItem>
        <SegmentedControlItem id="yearly">Yearly</SegmentedControlItem>
      </SegmentedControl>
      <p aria-live="polite">{billing === "yearly" ? "$190 a year, two months free." : "$19 a month."}</p>
    </div>
  );
}

Sizes

import { SegmentedControl, SegmentedControlItem } from "@rdloom/react";

export default function SegmentedControlSizesExample() {
  return (
    <div className="flex flex-col items-center gap-3">
      <SegmentedControl label="Density, small" size="sm" defaultSelectedKey="compact">
        <SegmentedControlItem id="compact">Compact</SegmentedControlItem>
        <SegmentedControlItem id="comfortable">Comfortable</SegmentedControlItem>
      </SegmentedControl>
      <SegmentedControl label="Density, medium" size="md" defaultSelectedKey="comfortable">
        <SegmentedControlItem id="compact">Compact</SegmentedControlItem>
        <SegmentedControlItem id="comfortable">Comfortable</SegmentedControlItem>
      </SegmentedControl>
    </div>
  );
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
labelrequired

What the control chooses, e.g. 'View'. It is the accessible name of the group.

stringnone
childrenrequired

SegmentedControlItem elements, each with an id.

nodenone
selectedKey

Controlled selected item id.

Keynone
defaultSelectedKey

Initial selected item id when uncontrolled.

Keynone
onChange

Called with the id of the chosen item.

(key: Key) => voidnone
size

Control height.

"sm" | "md""md"
isDisabled

Disables every option.

booleanfalse

Accessibility

Role radiogroup, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

  • Tab enters the group, on the chosen option
  • Arrow keys move to and choose the next or previous option
  • Space chooses the focused option

Screen readers announce

  • Announced as a radio group with the label, then each option as "radio button, N of M, checked or not checked"
  • Moving with arrow keys announces the newly chosen option

What your code must do

  • A radio group named by label, with each option a radio
  • The chosen option is marked by fill and weight and by aria-checked, not only by color
  • Exactly one option is always chosen
  • Selected text meets 4.5:1 against its background

Guidelines

Use it when

  • Switching between views of the same data: List, Board, Calendar
  • A setting with two to five short options, like Monthly or Yearly

Avoid it when

  • More than five options or long labels: use Select or RadioGroup
  • Choosing several at once: use checkboxes
  • Navigating to different pages: use Tabs or links

Don't

  • Using it as page navigation
  • Options that are not mutually exclusive
  • Icon-only options without a text label

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-surface-default
  • --rd-color-surface-subtle
  • --rd-color-border-default
  • --rd-color-action-primary
  • --rd-color-action-on-primary
  • --rd-color-text-default
  • --rd-color-focus-ring
  • --rd-radius-control