# Switcher

An accessible React segmented control for Next.js: one choice from a few options, with a pill that slides to the one you pick.

Source: https://ui.fewtone.com/components/switcher

## Installation

```bash
npx shadcn@latest add https://ui.fewtone.com/r/switcher.json
```

## Usage

```tsx
import { Switcher, SwitcherItem } from "@/components/ui/switcher"

export function RangeSwitcher() {
  return (
    <Switcher defaultValue="week" aria-label="Range">
      <SwitcherItem value="day">Day</SwitcherItem>
      <SwitcherItem value="week">Week</SwitcherItem>
      <SwitcherItem value="month">Month</SwitcherItem>
    </Switcher>
  )
}
```

## API reference

### Switcher

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | – | The chosen option, when you control it. |
| `defaultValue` | `string` | – | The option chosen at first. |
| `onValueChange` | `(value: string) => void` | – | Called with the new option. Clicking the chosen option again keeps it. |
| `variant` | `"default" \| "outline"` | `"default"` | Default: a muted track. Outline: a bordered track, with a bordered pill. The pill is dark in dark mode and light in light mode. |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | 32, 36 or 40px tall, matching Button. |
| `disabled` | `boolean` | `false` | Disables every option. |

### SwitcherItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | – | The value this option picks. |
| `disabled` | `boolean` | `false` | Skips the option. |

**When to use it:** Use Switcher for one choice from two to five short options that change a view, like Day / Week / Month or List / Grid. Use Toggle Group when options switch on and off independently, Select for longer lists, and Tabs when each option shows its own panel.
**Built on:** Radix Toggle Group.
**Accessibility pattern:** https://www.w3.org/WAI/ARIA/apg/patterns/radio/
**npm packages (installed by the CLI):** class-variance-authority@^0.7.1, motion@^14.0.0, radix-ui@^1.7.0.

## Examples

### Variants

Default has a muted track; Outline a bordered track and pill. The pill matches the theme, dark in dark mode and light in light mode. Pick an option and the pill slides to it. There's always a choice.

```tsx
"use client";

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Switcher, SwitcherItem } from "@/components/ui/switcher";
import { useId, useState } from "react";

type Size = "sm" | "md" | "lg";

const variants = [
  { variant: "default", label: "Default" },
  { variant: "outline", label: "Outline" },
] as const;

/*
 * Both variants side by side, named underneath, with the size in the top-right corner. An empty row at the
 * bottom, as tall as the setting's row, keeps them centred.
 */
export default function SwitcherVariants() {
  const id = useId();
  const [size, setSize] = useState<Size>("md");

  return (
    <div className="flex w-full flex-col gap-6 self-stretch">
      <div className="-mt-6 -mr-6 flex items-center justify-end gap-2">
        <label htmlFor={`${id}-size`} className="text-sm text-muted-foreground">
          Size
        </label>
        <Select value={size} onValueChange={(value) => setSize(value as Size)}>
          <SelectTrigger id={`${id}-size`} size="sm" className="w-28">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="sm">Small</SelectItem>
            <SelectItem value="md">Medium</SelectItem>
            <SelectItem value="lg">Large</SelectItem>
          </SelectContent>
        </Select>
      </div>

      <ul className="flex flex-1 flex-wrap items-center justify-center gap-x-10 gap-y-6">
        {variants.map(({ variant, label }) => (
          <li key={variant} className="flex flex-col items-center gap-2">
            <Switcher
              defaultValue="week"
              variant={variant}
              size={size}
              aria-label={`Range, ${label.toLowerCase()} style`}
            >
              <SwitcherItem value="day">Day</SwitcherItem>
              <SwitcherItem value="week">Week</SwitcherItem>
              <SwitcherItem value="month">Month</SwitcherItem>
              <SwitcherItem value="year">Year</SwitcherItem>
            </Switcher>
            <span className="text-xs text-muted-foreground">{label}</span>
          </li>
        ))}
      </ul>

      <div aria-hidden className="-mb-6 h-(--size-control-sm)" />
    </div>
  );
}
```

### Icons

Name each icon-only option with aria-label, so screen readers can tell them apart.

```tsx
import { Switcher, SwitcherItem } from "@/components/ui/switcher";
import { AlignCenterIcon, AlignLeftIcon, AlignRightIcon } from "lucide-react";

export default function SwitcherIcons() {
  return (
    <Switcher defaultValue="left" aria-label="Text alignment">
      <SwitcherItem value="left" aria-label="Align left">
        <AlignLeftIcon />
      </SwitcherItem>
      <SwitcherItem value="center" aria-label="Align centre">
        <AlignCenterIcon />
      </SwitcherItem>
      <SwitcherItem value="right" aria-label="Align right">
        <AlignRightIcon />
      </SwitcherItem>
    </Switcher>
  );
}
```

### Icons and text

```tsx
import { Switcher, SwitcherItem } from "@/components/ui/switcher";
import { LayoutGridIcon, ListIcon } from "lucide-react";

export default function SwitcherIconsAndText() {
  return (
    <Switcher defaultValue="list" variant="outline" aria-label="View">
      <SwitcherItem value="list">
        <ListIcon />
        List
      </SwitcherItem>
      <SwitcherItem value="grid">
        <LayoutGridIcon />
        Grid
      </SwitcherItem>
    </Switcher>
  );
}
```

## Related

- [Toggle Group](https://ui.fewtone.com/components/toggle-group.md): An accessible React toggle group for Next.js: buttons you switch on and off independently, like Bold, Italic and Underline in a toolbar.
- [Select](https://ui.fewtone.com/components/select.md): An accessible React select with a gliding highlight, groups, labels and a scrollable list, built on Radix Select.
