# Switch

> An on and off control for a setting that takes effect right away, with a built-in label, tones, and sizes.

- Name: `switch`
- Status: beta
- Categories: forms
- File: the install writes `switch.tsx` to your `ui` alias, `@/components/ui` by default.
- npm packages: `@base-ui/react`, `class-variance-authority`. The install adds them.
- Gallery: https://txbrendan.dev/components/switch
- Registry item: https://txbrendan.dev/r/switch.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@txbrendan": "https://txbrendan.dev/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@txbrendan` in a public list that txbrendan/ui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @txbrendan/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @txbrendan/switch
   ```

## Use it for

- Turning a setting on or off when the change applies at once, like email alerts or auto-save.
- Pass the label text as children. Name the setting, like 'Email alerts', not the action, like 'Turn on alerts'.
- A settings row with the text first: give the switch an id and point a label's htmlFor at it.
- Use tone to color the track when it is on. Keep neutral unless 'on' needs to stand out.

## Avoid it for

- A choice that only applies after the person presses Save. Use a checkbox.
- Picking options from a list. Use checkboxes or a radio group.
- An id with no label pointing at it. TypeScript accepts the id, but the switch then has no name.

## Props

- `children`: `ReactNode`. Visible label text. Without it, TypeScript requires aria-label, aria-labelledby, or an id.
- `id`: `string`. Links a separate label. A label whose htmlFor matches names the switch, and clicking it flips the switch.
- `tone`: `"neutral" | "brand" | "danger" | "success" | "warning"`. Default `"neutral"`. Color of the track when it is on.
- `size`: `"sm" | "md" | "lg"`. Default `"md"`. Track size and label text size. Density scales them.
- `checked`: `boolean`. Controlled state. Use it with onCheckedChange.
- `defaultChecked`: `boolean`. Default `false`. Starting state when you do not control it.
- `onCheckedChange`: `(checked: boolean, details) => void`. Called with the new state when someone flips the switch.
- `disabled`: `boolean`. Default `false`. Blocks flipping and removes the switch from the tab order. The label text dims too.
- `name`: `string`. Form field name. The switch sends its value with the form when it is on.
- `aria-invalid`: `boolean`. Shows the danger border. Set it when validation fails, and link the message with aria-describedby.
- `className`: `string`. Classes for the track. Later classes win, so className='\[--switch-h:2rem\]' makes it bigger.

## Accessibility

- Renders a span with role="switch" and aria-checked plus a hidden native checkbox, so the value is sent with the form.
- Space and Enter both flip it.
- TypeScript requires visible children, aria-label, aria-labelledby, or an id. It cannot check that a label points at the id.
- In server HTML, only aria-label and aria-labelledby name the switch. A label from children or an id is linked once JavaScript runs.
- Clicking the label text also flips it, so the target is bigger than the track.
- The off track uses --input, which the theme tests hold to 3:1 against the page, so the off state stays visible.
- The thumb on the on track uses the tone's foreground, which the theme tests hold to 4.5:1 against the tone.
- The focus ring uses --ring, which the theme tests hold to 3:1 against the page and cards.
- In forced colors the thumb fills with the system text color, and an on track fills with the system highlight, so on and off still look different.
- The thumb slides with --motion-\* tokens. Reduced motion keeps the color change and drops the slide.

## Examples

### With a label

```tsx
<Switch name="alerts" defaultChecked>
  Email alerts
</Switch>
```

### Controlled

```tsx
"use client";
import { useState } from "react";

function AutoSave() {
  const [on, setOn] = useState(true);
  return (
    <Switch checked={on} onCheckedChange={setOn} tone="brand">
      Auto-save drafts
    </Switch>
  );
}
```

### Settings row with the text first

```tsx
<div className="flex items-center justify-between gap-4">
  <label htmlFor="sync" className="text-sm">
    Sync over mobile data
  </label>
  <Switch id="sync" />
</div>
```

### No visible text, in a toolbar

```tsx
<Switch size="sm" aria-label="Show weekends" />
```
