Skip to content
WANCRAFT

A.93Buttons & Controls

Segmented Control

A radio-group segmented control with a spring thumb, roving arrow keys, sizes and native form support.

NewReactMotionTailwindbasic

Overview

Segmented Control picks one value from a short set — a view mode, a time range, a unit. It is a real radio group, not a tab list: one option is always checked, the arrow keys move focus and selection together, and Tab enters and leaves the group in one stop. A single thumb slides between options with a shared-layout spring, hairline dividers fade around it, and a hidden input submits the value with a form. Options can carry icons, be disabled individually, or collapse to icon-only buttons that keep their labels as accessible names.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/segmented-control.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/segmented-control.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/segmented-control.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { SegmentedControl, type SegmentedOption } from "@/components/ui/segmented-control";

export function SegmentedControlExample() {
  const options: SegmentedOption[] = [
    { value: "day", label: "Day" },
    { value: "week", label: "Week" },
    { value: "month", label: "Month" },
  ];

  return (
    <SegmentedControl
      options={options}
      defaultValue="week"
      label="Range"
      name="range"
    />
  );
}

03Props

PropTypeDefaultDescription
optionsSegmentedOption[]—{ value: string; label: string; icon?: ReactNode; disabled?: boolean }.
valuestring—Selected value (controlled).
defaultValuestring—Initially selected value (uncontrolled). Defaults to the first enabled option.
onValueChange(value: string) => void—Called when the user picks another option.
labelstring—Accessible name of the radio group (or pass aria-labelledby).
size"sm" | "md" | "lg""md"Height, padding and type size.
variant"neutral" | "accent""neutral"Raised neutral thumb or accent-filled thumb.
fullWidthbooleanfalseStretch to the container and share the width equally.
iconOnlybooleanfalseShow icons only; each label becomes the option's aria-label and tooltip.
spring{ stiffness: number; damping: number; mass: number }{ stiffness: 420, damping: 34, mass: 0.8 }Spring that slides the thumb.
accentstring"#FF7448"Accent thumb fill and focus ring colour.
disabledbooleanfalseDisable the whole control (the hidden input is disabled too).
namestring—Adds a hidden input with this name so the value submits with a form.
...propsHTMLAttributes<HTMLDivElement>—Forwarded to the role="radiogroup" element.

04Features

  • 01Radio-group semantics: always exactly one checked option
  • 02Single thumb that slides between options with a shared-layout spring (scoped per instance)
  • 03Controlled (value + onValueChange) or uncontrolled (defaultValue)
  • 04Three sizes, full-width mode, icon-only mode and an accent thumb variant
  • 05Per-option disabled state; arrow keys skip disabled options
  • 06Hidden input for native form submission
motion · layoutmotion · springinput · clickinput · keyboardinput · focus

05Accessibility

  • role="radiogroup" containing role="radio" buttons with aria-checked; name the group with label or aria-labelledby.
  • Roving tabindex: Tab enters on the checked option; arrow keys move focus and selection together and wrap; Home / End jump to the ends.
  • Disabled options use the native disabled attribute and are skipped by the arrow keys.
  • Icon-only options keep their text label as aria-label and title.
  • Visible accent focus outline; with prefers-reduced-motion the thumb moves instantly.

06Limitations

  • Horizontal only. Arrow keys follow left-to-right order and are not mirrored for RTL layouts.