Inputs
Segmented control
A compact row of mutually exclusive options, for switching views, filters or modes in place. The selected background slides between segments, and it behaves like a radio group: one tab stop, arrow keys to move.
<div className="flex justify-center"> <SegmentedControl aria-label="Project view" defaultValue="board" options={[ { value: "list", label: "List", icon: <List strokeWidth={1.75} /> }, { value: "board", label: "Board", icon: <Columns3 strokeWidth={1.75} /> }, { value: "calendar", label: "Calendar", icon: <CalendarDays strokeWidth={1.75} /> }, ]} /></div>Usage
components/ui/segmented-control.tsx
import { SegmentedControl } from "@/components/ui/segmented-control"; export function Example() { return ( <SegmentedControl aria-label="View" defaultValue="list" options={[ { value: "list", label: "List" }, { value: "board", label: "Board" }, ]} /> );}Examples
View switcher
<div className="flex justify-center"> <SegmentedControl aria-label="Project view" defaultValue="board" options={[ { value: "list", label: "List", icon: <List strokeWidth={1.75} /> }, { value: "board", label: "Board", icon: <Columns3 strokeWidth={1.75} /> }, { value: "calendar", label: "Calendar", icon: <CalendarDays strokeWidth={1.75} /> }, ]} /></div>Sizes
32, 36 and 40px tall, matching buttons and inputs.
<div className="flex flex-col items-center gap-4"> <SegmentedControl size="sm" aria-label="Period" defaultValue="7d" options={[{ value: "24h", label: "24h" }, { value: "7d", label: "7d" }, { value: "30d", label: "30d" }, { value: "12m", label: "12m" }]} /> <SegmentedControl size="md" aria-label="Period" defaultValue="7d" options={[{ value: "24h", label: "24h" }, { value: "7d", label: "7d" }, { value: "30d", label: "30d" }, { value: "12m", label: "12m" }]} /> <SegmentedControl size="lg" aria-label="Period" defaultValue="7d" options={[{ value: "24h", label: "24h" }, { value: "7d", label: "7d" }, { value: "30d", label: "30d" }, { value: "12m", label: "12m" }]} /></div>Icon only
Leave out label and give each option an aria-label.
<div className="flex flex-wrap justify-center gap-4"> <SegmentedControl aria-label="Theme" defaultValue="system" options={[ { value: "light", icon: <Sun strokeWidth={1.75} />, "aria-label": "Light" }, { value: "dark", icon: <Moon strokeWidth={1.75} />, "aria-label": "Dark" }, { value: "system", icon: <Monitor strokeWidth={1.75} />, "aria-label": "System" }, ]} /> <SegmentedControl aria-label="Text alignment" defaultValue="left" options={[ { value: "left", icon: <AlignLeft strokeWidth={1.75} />, "aria-label": "Align left" }, { value: "center", icon: <AlignCenter strokeWidth={1.75} />, "aria-label": "Align center" }, { value: "right", icon: <AlignRight strokeWidth={1.75} />, "aria-label": "Align right", disabled: true }, ]} /></div>Full width
<div className="mx-auto w-full max-w-md"> <SegmentedControl fullWidth aria-label="Invoice status" defaultValue="all" options={[ { value: "all", label: "All" }, { value: "draft", label: "Draft" }, { value: "sent", label: "Sent" }, { value: "paid", label: "Paid" }, ]} /></div>In context
A controlled control driving a pricing card.
$24 / seat / month
Billed $288 per seat each year.
"use client"; import { useState } from "react";import { SegmentedControl } from "@/components/ui/segmented-control"; export function BillingCycleDemo() { const [cycle, setCycle] = useState("yearly"); const price = cycle === "yearly" ? 24 : 29; return ( <div className="mx-auto flex w-full max-w-xs flex-col items-center gap-5 rounded-ui-lg border border-line bg-surface p-6 text-center shadow-ui"> <SegmentedControl aria-label="Billing cycle" value={cycle} onValueChange={setCycle} options={[ { value: "monthly", label: "Monthly" }, { value: "yearly", label: "Yearly · save 17%" }, ]} /> <div className="flex flex-col gap-1"> <p className="text-title tabular"> ${price} <span className="text-[15px] font-normal tracking-normal text-muted"> / seat / month</span> </p> <p className="text-[13px] leading-5 text-muted">{cycle === "yearly" ? "Billed $288 per seat each year." : "Billed monthly, cancel any time."}</p> </div> </div> );}Props
PropTypeDefaultDescription
optionsrequiredSegmentedOption[]default: —{ value, label?, icon?, aria-label?, disabled? } for each segment.value / defaultValuestringdefault: —Controlled or initial value. Defaults to the first option.onValueChange(value: string) => voiddefault: —Called when the selection changes.aria-labelstringdefault: —Names the group. Or use aria-labelledby.size"sm" | "md" | "lg"default: "md"Height 32 / 36 / 40px.fullWidthbooleandefault: falseFill the container; segments share the width.disabledbooleandefault: —Disable every segment.
Accessibility
- Uses the radio group pattern: role="radiogroup" with role="radio" segments and aria-checked.
- Only the selected segment is in the tab order. Arrow keys move and select (wrapping around), Home and End jump to the ends; disabled segments are skipped.
- Icon-only segments need an aria-label each; the icon is decorative.
- Use it for a handful of short options that change the view in place. For page sections, use Tabs; for form answers, use RadioGroup.