# Switch

Turns one setting on or off, and takes effect as soon as it is flipped.

- **Import:** `import { Switch } from "@booleanpress/ui/switch"`
- **Radix Switch:** <https://www.radix-ui.com/primitives/docs/components/switch>
- **APG Switch:** <https://www.w3.org/WAI/ARIA/apg/patterns/switch/>
- **Page:** <https://ui.booleanpress.com/components/switch> · @booleanpress/ui 0.1.0

## Usage

Give every switch a visible name: a `Label` whose `htmlFor` is the switch's `id`. Clicking the label flips it.

```tsx
import { Label } from "@booleanpress/ui/label"
import { Switch } from "@booleanpress/ui/switch"

export function LogSetting() {
  return (
    <div className="flex items-center gap-2">
      <Switch id="log-emails" defaultChecked />
      <Label htmlFor="log-emails">Keep the email log</Label>
    </div>
  )
}
```

It is uncontrolled with `defaultChecked`, or controlled with `checked` and `onCheckedChange`. `size` is `default` or `sm`. Use a switch for a setting that applies at once; use a `Checkbox` for a choice that is submitted with a form or that belongs to a list of options.

## Examples

### Basic

Off and on, each named by its label.

```tsx
import { Label } from "@booleanpress/ui/label"
import { Switch } from "@booleanpress/ui/switch"

export default function SwitchBasic() {
  return (
    <div className="flex flex-col gap-3">
      <div className="flex items-center gap-2">
        <Switch id="log-emails" defaultChecked />
        <Label htmlFor="log-emails">Keep the email log</Label>
      </div>
      <div className="flex items-center gap-2">
        <Switch id="track-opens" />
        <Label htmlFor="track-opens">Track opens</Label>
      </div>
    </div>
  )
}
```

### Controlled

`checked` and `onCheckedChange` keep the setting in your state, here shown beneath the switch.

```tsx
import { useState } from "react"
import { Label } from "@booleanpress/ui/label"
import { Switch } from "@booleanpress/ui/switch"

export default function SwitchControlled() {
  const [enabled, setEnabled] = useState(true)

  return (
    <div className="flex flex-col gap-3">
      <div className="flex items-center gap-2">
        <Switch id="failure-alerts" checked={enabled} onCheckedChange={setEnabled} />
        <Label htmlFor="failure-alerts">Email me when a send fails</Label>
      </div>
      <p className="text-sm text-muted-foreground">Alerts are {enabled ? "on" : "off"}.</p>
    </div>
  )
}
```

### Sizes

`default` is 32 × 18 px; `sm` is 24 × 14 px, for dense rows.

```tsx
import { Label } from "@booleanpress/ui/label"
import { Switch } from "@booleanpress/ui/switch"

export default function SwitchSizes() {
  return (
    <div className="flex flex-col gap-3">
      <div className="flex items-center gap-2">
        <Switch id="size-default" defaultChecked />
        <Label htmlFor="size-default">Default</Label>
      </div>
      <div className="flex items-center gap-2">
        <Switch id="size-sm" size="sm" defaultChecked />
        <Label htmlFor="size-sm">Small</Label>
      </div>
    </div>
  )
}
```

### Disabled

A disabled switch keeps its state, ignores clicks and keys, and leaves the tab order.

```tsx
import { Label } from "@booleanpress/ui/label"
import { Switch } from "@booleanpress/ui/switch"

export default function SwitchDisabled() {
  return (
    <div className="flex flex-col gap-3">
      <div className="flex items-center gap-2">
        <Switch id="queue-off" disabled />
        <Label htmlFor="queue-off">Send through the queue</Label>
      </div>
      <div className="flex items-center gap-2">
        <Switch id="queue-on" disabled defaultChecked />
        <Label htmlFor="queue-on">Retry failed sends</Label>
      </div>
    </div>
  )
}
```

### Invalid

`aria-invalid` and `aria-describedby` tell assistive technology about the error; the switch draws no error border, so show the message as text.

```tsx
import { Label } from "@booleanpress/ui/label"
import { Switch } from "@booleanpress/ui/switch"

export default function SwitchInvalid() {
  return (
    <div className="flex flex-col gap-2">
      <div className="flex items-center gap-2">
        <Switch id="consent" required aria-invalid aria-describedby="consent-error" />
        <Label htmlFor="consent">Store recipient addresses in the log</Label>
      </div>
      <p id="consent-error" className="text-sm text-destructive">
        Turn this on to keep the log searchable.
      </p>
    </div>
  )
}
```

## Accessibility

**Semantics.** A `button` with `role="switch"` and `aria-checked` (`true` or `false`); the thumb is decorative. Inside a form, it also renders a hidden native checkbox, so the value is submitted with the form.

**Labels.** Name every switch: a `Label` with `htmlFor`, or `aria-label` when no visible text fits. Word the label for the "on" state ("Keep the email log"), because a screen reader adds "on" or "off" itself.

**Focus.** It is in the tab order. The focus ring shows on keyboard focus, not on click.

**Known limits.**

- The default track is 18 px high and the small one 14 px. Keep the label beside the switch, so the label adds to the target and the pair meets WCAG 2.5.8's 24 px.
- Unlike the other form controls, the switch has no error border for `aria-invalid`; the visible error message is the only sign for sighted people.

### Keyboard

| Key | Behaviour |
| --- | --- |
| Space | Flips the switch. |
| Enter | Flips the switch. APG lists Enter as optional. |

## API

### Switch

Renders Radix Switch.Root and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  | Render the child element instead, with this part's behaviour and classes merged onto it. |
| `checked` | `boolean` |  | The state, when you control it. Pair it with `onCheckedChange`. |
| `defaultChecked` | `boolean` |  | The state it starts in, when it controls itself. |
| `onCheckedChange` | `((checked: boolean) => void)` |  | Called with the new state when it is flipped. |
| `required` | `boolean` |  | The form cannot be submitted until it is on. |
| `size` | `"default" \| "sm"` | `default` | `default` or `sm`. |

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="switch"` (Switch), and `data-size`.

## Theming

The track is `--primary` when on and `--control` when off, at least 3:1 against the surface; the thumb is `--background`. The thumb slides toward the end of the line, so it mirrors in right-to-left.

| Token | Used for |
| --- | --- |
| `--background` | background |
| `--control` | background |
| `--foreground` | background |
| `--input` | background |
| `--primary` | background |
| `--primary-foreground` | background |
| `--ring` | border, focus ring |
