Skip to content
Suluu

SegmentedControl

A quiet single-choice group. One pill sits under the selected option and slides to the next on a soft spring, squashing slightly as it travels the way the switch thumb does.

Preview

Click an option or use the arrow keys

Installation

Registry — recommended

Copies the component into your configured shadcn UI directory and installs Motion.

npx shadcn@latest add https://suluu.site/r/segmented-control.json

npm package

For centralized upgrades, install the optional ESM package and configure Tailwind source detection as shown below.

pnpm add suluu motion

Usage

Give every group a stable accessible name. Use defaultValue for local state, or pair value with onValueChange when your application owns the selection.

Registry
import { SegmentedControl } from "@/components/ui/segmented-control"

export function RangeFilter() {
  return (
    <SegmentedControl
      aria-label="Range"
      defaultValue="week"
      options={[
        { value: "day", label: "Day" },
        { value: "week", label: "Week" },
        { value: "month", label: "Month" },
      ]}
    />
  )
}
npm
// app/globals.css
@import "suluu/styles.css";
@source "../node_modules/suluu/dist";

// component.tsx
import { SegmentedControl } from "suluu/segmented-control"

export function RangeFilter() {
  return (
    <SegmentedControl
      aria-label="Range"
      defaultValue="week"
      options={[
        { value: "day", label: "Day" },
        { value: "week", label: "Week" },
        { value: "month", label: "Month" },
      ]}
    />
  )
}

Controlled state

Controlled
const [range, setRange] = useState("week")

<SegmentedControl
  aria-label="Range"
  onValueChange={setRange}
  options={[
    { value: "day", label: "Day" },
    { value: "week", label: "Week" },
    { value: "month", label: "Month" },
  ]}
  value={range}
/>

In context

A compact range selector keeps an analytics view legible while the moving pill preserves selection context.

Active readers

7,420

+8.4% from the previous period

Props

SegmentedControl accepts safe native div attributes, always uses role="radiogroup", and forwards its ref to the group element. Each option may also set disabled.

PropTypeDefaultDescription
optionsSegmentedControlOption[]Choices rendered as radio options.
valuestringControlled selected value.
defaultValuestringFirst optionInitial uncontrolled selected value.
onValueChange(value) => voidRuns when a click or key press requests a new value.
disabledbooleanfalseDisables every option in the group.
classNamestringClass name applied to the group.

Theming

Override these variables in your light and dark theme scopes. The registry installs the defaults automatically.

--suluu-segment-backgroundTrack surface.
--suluu-segment-foregroundSelected label.
--suluu-segment-mutedUnselected label.
--suluu-segment-pillSliding indicator surface.
--suluu-segment-ringKeyboard focus ring.
--suluu-segment-offsetColor behind the focus ring offset.
--suluu-segment-shadowTrack inset depth.
--suluu-segment-pill-shadowPill elevation.

The root exposes data-state for the selected value. Each option exposes data-state as checked or unchecked.

Accessibility

The segmented control is a radiogroup of native buttons. Arrow keys, Home, and End move the selection, only the selected option is in the tab order, and disabled state blocks every input path.

With reduced motion, the pill still marks the selected option but the slide and squash become immediate.

↑↓ to navigate↵ to openesc to close