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 verifiedUses 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.jsonMethod 02 · Copy and paste
- 1. Install the dependencies below.
- 2. Copy the source from the Code tab into
components/ui/segmented-control.tsx. - 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.
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
| Prop | Type | Default | Description |
|---|---|---|---|
| options | SegmentedOption[] | — | { value: string; label: string; icon?: ReactNode; disabled?: boolean }. |
| value | string | — | Selected value (controlled). |
| defaultValue | string | — | Initially selected value (uncontrolled). Defaults to the first enabled option. |
| onValueChange | (value: string) => void | — | Called when the user picks another option. |
| label | string | — | 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. |
| fullWidth | boolean | false | Stretch to the container and share the width equally. |
| iconOnly | boolean | false | Show 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. |
| accent | string | "#FF7448" | Accent thumb fill and focus ring colour. |
| disabled | boolean | false | Disable the whole control (the hidden input is disabled too). |
| name | string | — | Adds a hidden input with this name so the value submits with a form. |
| ...props | HTMLAttributes<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
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.