# Switch

> A switch with weight: the thumb stretches on press, is thrown across with a slight overshoot, and spins while an async save settles.

Settings switches usually flip instantly and lie when the save fails. This one flips at once, spins in its thumb while your promise runs, and throws itself back with a small shake if it rejects. Pressing stretches the thumb toward where it will go, and the track fills in step.

## Install

```bash
npx shadcn@latest add https://hairlineui.com/r/switch.json
```

Needs React 19, Tailwind CSS v4 and shadcn/ui theme variables (any style, light and dark). Icons from lucide-react.

- npm: none
- Hairline UI components it needs (the CLI installs them too): `ui-interaction-motion`
- Docs and live preview: https://hairlineui.com/ui/switch
- Registry item (source): https://hairlineui.com/r/switch.json

## Usage

```tsx
import { Switch } from "@/components/ui/switch";

<Switch
  label="Weekly digest"
  description="A summary every Monday."
  labelPosition="start"
  defaultChecked
  onCheckedChange={(on) => saveSetting("digest", on)}
/>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `checked / defaultChecked` | `boolean` | – | State. |
| `onCheckedChange` | `(checked) => void \| Promise` | – | Return a promise for the pending thumb; reject to put it back. |
| `label / description` | `ReactNode` | – | Text beside the switch. |
| `labelPosition` | `"start" \| "end"` | `"end"` | Label before or after. |
| `size` | `"sm" \| "md"` | `"md"` | 16px or 20px tall. |

## States

- **off / on**: The thumb is thrown across (420ms, slight overshoot) and the primary fill fades in (260ms).
- **press**: The thumb stretches 4px toward its next side.
- **pending**: The thumb shows a spinner; the switch ignores presses.
- **failed**: The thumb is thrown back and the track shakes (320ms).

## What it handles

An optimistic toggle needs pending state, rollback and an error signal, and most switches leave all three to you.

- **Async without wiring**: Return a promise from onCheckedChange: pending spinner, then settled or thrown back.
- **Weight in the motion**: The thumb stretches 4px on press and is thrown with an overshoot (420ms).
- **role="switch"**: aria-checked and aria-busy, Space and Enter.
- **Label either side**: labelPosition="start" for settings rows with the switch on the right.

## Replaces

optimistic toggle state, rollback on error, loading indicator

## Where it fits

- **Settings pages**: Notification and privacy toggles that save as you flip them.
- **Feature flags**: Turning a flag on in an admin panel with the request in flight.
- **Table rows**: An Active column you can flip inline.
- **Pricing**: A compact on/off beside an option.

## Notes

- Reduced motion: the thumb moves at once and nothing shakes.
- While pending, aria-busy is set and further presses are ignored.

License: MIT (https://hairlineui.com/license#components)
