Segmented control

Switch between a small set of related views.

controlchoice
Preview
Segmented control preview

When to use

  • Two to five short view options like Day, Week, and Month.
  • Toolbar toggles between layouts or modes that apply immediately.

When not to use

  • Use tabs when each option swaps a panel of content.
  • Use radio-group in forms or when options need descriptions.
  • Use switch for a single on and off setting.

shadcn CLI

Also installs (1)

  • manicat-foundation
Manual install(+)
registry.jsonjson
{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "segmented-control",
  "type": "registry:ui",
  "title": "Segmented control",
  "description": "Switch between a small set of related views.",
  "categories": [
    "inputs"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://manicat.viraza.net/r/manicat-foundation.json"
  ],
  "files": [
    {
      "path": "registry/components/segmented-control/segmented-control.tsx",
      "type": "registry:component",
      "target": "@components/manicat/segmented-control/segmented-control.tsx"
    },
    {
      "path": "registry/components/segmented-control/segmented-control.module.css",
      "type": "registry:component",
      "target": "@components/manicat/segmented-control/segmented-control.module.css"
    }
  ],
  "docs": "Docs and live preview: https://uiarc.dev/components/segmented-control",
  "meta": {
    "tier": "free",
    "kind": "component",
    "docs": "https://manicat.viraza.net/components/segmented-control",
    "markdown": "https://uiarc.dev/components/segmented-control/markdown",
    "tags": [
      "control",
      "choice"
    ]
  }
}

Usage

Install the component, then copy this into your app. Change the content and the props to fit your screen.

example.tsxtsx
"use client";

import { useState } from "react";
import SegmentedControl from "@/components/manicat/segmented-control/segmented-control";

export function SegmentedControlExample() {
  const [value, setValue] = useState("grid");

  return (
    <SegmentedControl
      label="Layout"
      value={value}
      onValueChange={setValue}
      options={[
        { value: "grid", label: "Grid" },
        { value: "list", label: "List" },
        { value: "map", label: "Map" },
      ]}
    />
  );
}

Examples

Real screens rather than isolated props. Switch to Code to copy a whole example into your project.

View switcher Switch a project list between layouts; the choice drives what renders below.

View switcher Switch a project list between layouts; the choice drives what renders below. preview
Showing the board view

Keyboard interactions

KeysAction
TabMoves focus into the control, onto the selected segment, and out again.
Arrow keysSelect and focus the previous or next segment, wrapping at the ends.
Home / EndSelect the first or last segment.

Accessibility

  • Renders role="group" labelled by label, with native buttons using aria-pressed for the selected segment. Only the selected segment is a tab stop.
  • The sliding pill is aria-hidden.

Motion

  • A shared layoutId pill slides to the selected segment on the morph spring, scoped per instance by a LayoutGroup.
  • Reduced motion moves the pill instantly and disables button transitions.

Responsive behavior

  • The row is inline with max-width 100% and scrolls horizontally with a hidden scrollbar when segments do not fit.
  • Segments never shrink or wrap, so keep labels short on mobile.

Performance

  • One shared layoutId pill per instance, scoped by a LayoutGroup; no observers.

Notes for AI

  • Use for two to five short, mutually exclusive view options like time ranges or layouts. Use tabs when each option swaps a panel, and radio-group in forms.
  • Always controlled. Import it as a default export.