Segmented Control React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React iOS-style segmented control with a raised thumb that slides and follows your finger.
Click or tap an option and the raised thumb glides over to it on a quick spring; the hairlines next to it fade away. Press the thumb and it dips a little; keep holding and slide sideways to drag it across the options, the value follows the finger and settles where you let go. A vertical swipe still scrolls the page. With the keyboard the control is one tab stop: Left and Right (Up and Down too) move and select, Home and End jump to the ends, and disabled options are skipped. Icon-only options show their name in a tooltip on hover and focus, or on a long press on touch. On a phone the medium size is 44px tall and fullWidth shares the width between options. With dir set to rtl the arrows and the drag mirror.
Segmented Control is a free React component in the React input components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add segmented-control, together with Badge, Tooltip, Motion presets, Tones, useControllableState and 1 more and the npm packages @radix-ui/react-radio-group and motion.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add segmented-controlUsage
Import SegmentedControl from @/components/ui/segmented-control.
import { useState } from "react"
import { SegmentedControl, type SegmentedControlOption } from "@/components/ui/segmented-control"
const periods: SegmentedControlOption[] = [
{ value: "day", label: "Day" },
{ value: "week", label: "Week" },
{ value: "month", label: "Month" },
{ value: "year", label: "Year" },
]
export function SalesPeriod() {
const [period, setPeriod] = useState("week")
return (
<SegmentedControl
aria-label="Period"
options={periods}
value={period}
onValueChange={setPeriod}
fullWidth
/>
)
}Props
options*SegmentedControlOption[]The options: { value, label?, icon?, disabled?, badge?, tooltip? }. 2 to 5 read best; more still fit with truncated labels (use tabs then). A number badge renders a small count that hides at 0.
valuestringThe selected option, when you control it. A value that is not in options shows no thumb.
defaultValuedefault""stringThe option selected on the first render when uncontrolled. Empty, or a value that is not in options, starts on the first enabled option.
onValueChange(value: string) => voidCalled with the new value on click, arrow keys, Home / End and while dragging the thumb.
sizedefault"md""sm" | "md" | "lg"Track height 32 / 40 / 48px with matching type and icon size; options fill it inside a 2px padding. On touch screens md grows to 44px and sm keeps its look with a 44px hit area.
radiusdefault"md""none" | "sm" | "md" | "lg" | "full"Corner shape of the track, scaled with the size like the button's. The thumb is 2px rounder-in so the gap stays even.
tonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"The thumb's color. neutral is a raised white surface (a lifted gray in dark mode) like iOS; the other tones fill the thumb and turn its label to the tone's foreground.
colordefault""stringAny CSS color for the thumb. Replaces the tone; the label on it turns black or white by lightness. Leave empty to use the tone.
dividersdefaulttruebooleanHairlines between unselected neighbours. The ones touching the thumb fade out, like iOS.
iconOnlydefaultfalsebooleanShows only the icons. Each label becomes the option's aria-label and its tooltip, so the control stays readable.
fullWidthdefaultfalsebooleanStretches the track to its container and shares the width between options. The usual layout on a phone.
draggabledefaulttruebooleanPress the thumb and slide sideways to change the value, with touch or a mouse. Vertical swipes still scroll the page.
loopdefaulttruebooleanArrow keys wrap from the last option to the first and back. Off, they stop at the ends.
disableddefaultfalsebooleanDims the whole control and blocks it. Disable single options with option.disabled.
aria-labeldefault""stringThe group's name for screen readers. Required when no visible label names the control.
namedefault""stringForm field name. Inside a <form> the selected value is submitted like a radio group.
dirdefault"ltr""ltr" | "rtl"Reading direction; rtl mirrors the layout, the arrow keys and the drag. Left out, it follows a Radix DirectionProvider, else ltr.
classNamesPartial<Record<"item" | "thumb" | "icon" | "label" | "badge" | "divider", string>>Extra classes for the inner parts.
classNamestringExtra classes for the track, merged last.
Parts
SegmentedControlOption
typeOne option: value, label, icon, disabled, badge (a number renders a count badge, hidden at 0) and tooltip (shown on hover, focus and long press).
Examples
Sizes and radii
32, 40 and 48px tracks; the thumb stays 2px rounder-in at every radius.
Icon-only view switch
Labels move into aria-labels and tooltips (hover, focus or long press).
Full width on a phone
fullWidth shares the width; drag the thumb across with a finger.
Tones and a custom color
neutral is the raised iOS thumb; tones and any CSS color fill it.
With badges
A number renders a count badge that tints with the thumb.
In a form
name="delivery" submits the value like a radio group.
Disabled
A single option locked behind a plan, and a whole disabled group.
Files
- components/ui/segmented-control.tsx
FAQ
Is the React Segmented Control component free?
Yes. The source is free and needs no account: copy it from the Code tab or install it with the CLI.
How do I install Segmented Control?
Run npx wingo-ui@latest add segmented-control. It also adds the Wingo UI items it builds on (Badge, Tooltip, Motion presets, Tones, useControllableState and 1 more) and the npm packages @radix-ui/react-radio-group and motion. With shadcn, run npx shadcn@latest add https://wingo-ui.com/r/segmented-control.json.
How does Segmented Control behave on phones?
On a phone the medium size is 44px tall and fullWidth shares the width between options.
See also
Used in
More React input components
Articles using this component
- React Swipeable Tabs: Pages That Follow the Thumb
Mobile UI in React10 min read
- Shadcn Pricing Page: Yearly Toggle, Table, Phone Layout
Build it: screens and apps10 min read
- Framer Motion Spring Presets: Stiffness and Damping
Component guides9 min read
- React Segmented Control: iOS Thumb, Drag and When to Use It
Mobile UI in React8 min read