# Switch

A setting that takes effect immediately.

- Group: Forms
- Import: `import { Switch } from '@misoto22/folio'`
- Page: https://ui.misoto22.com/components/switch/
- Related: checkbox

## When to reach for it

Inside a form with a Save button, a switch is a lie about when the change happened. Use a Checkbox.

## Anatomy

- **Track** (required) — A 36×20 <button role="switch"> on a pill radius, filled --stone when off and --accent when on. Filled rather than outlined, so it still reads as a control on a white page.
- **Thumb** (required) — A 14px paper circle with a hairline, not a white disc on a shadow — this system has no shadows. It stretches to 20px while pressed and rounds out as it lands; motion-reduce drops that entirely.
- **Label** — Not rendered here either. The Radix root IS a <button>, though, which a <label for> does bind to — so unlike Select or RadioGroup, a Field’s label above a Switch really does click through to it.

## Best practices

### Do

- Handle the failure at the control. The flip has already claimed the change happened, so an onCheckedChange whose request fails must put the thumb back and say why, or the page is showing a setting the server does not have.
- Name it for the state, not the action: the accessible name is read together with “on” or “off”, so “Email notifications, on” is a sentence and “Turn on email notifications, on” is two contradictory ones.
- Keep the flip instant to the eye even when the write is not — an optimistic thumb with a quiet undo beats a spinner on a control whose whole claim is that it already took effect.

### Don’t

- A switch has two states and no third. “Inherit from the workspace” cannot be a switch, because the only way to draw it is unchecked, which announces “off” — that is a RadioGroup or a Select.
- There is no readOnly: disabled is the only lock, and it takes the control out of the tab order, so a keyboard reader tabbing the form passes the setting without ever hearing its value.
- Do not add transition-all from a call site. It replaces transition-[transform,width] wholesale, which puts the track’s colour on the thumb’s longer duration and turns a flip into a fade.

## Keyboard

- Space / Enter — Toggles it, and the change applies immediately.

## Switch

A setting that takes effect immediately. Distinct from `Checkbox`, and the distinction is not cosmetic: a switch applies on flip, a checkbox applies on submit. A switch inside a form with a Save button is a lie about when the change happened. The track fills with ink when on and the thumb is paper with a hairline — rather than a white thumb floating on a drop shadow, which this system does not have. The off state is a filled rule-coloured track, so the control still reads as a control on a white page.

Also accepts: `ComponentProps<typeof SwitchPrimitive.Root>`.

## Example — default

```tsx
import { Switch } from '@misoto22/folio'

<div className="flex flex-col gap-3">
  {SETTINGS.map((setting) => (
    <label key={setting.id} className="flex cursor-pointer items-center gap-3 text-sm">
      <Switch
        checked={state[setting.id]}
        onCheckedChange={(next) => setState((previous) => ({ ...previous, [setting.id]: next }))}
      />
      {/* The label follows the state. A switch that is off beside a label
          at full strength reads as "on" at a glance, and the control is
          the smaller of the two things on the row. */}
      <span
        className={
          state[setting.id]
            ? 'text-(--ink) transition-colors duration-(--duration-fast)'
            : 'text-(--ink-3-aa) transition-colors duration-(--duration-fast)'
        }
      >
        {setting.label}
      </span>
      <span className="ms-auto mono-meta text-(--ink-3-aa)">
        {state[setting.id] ? 'On' : 'Off'}
      </span>
    </label>
  ))}
</div>
```

## Example — a failed write

```tsx
import { Alert, Switch } from '@misoto22/folio'

<div className="flex max-w-sm flex-col gap-3">
  <label className="flex cursor-pointer items-center gap-3 text-sm text-(--ink-2)">
    <Switch checked={on} onCheckedChange={save} />
    Two-factor authentication
  </label>
  {failed && (
    <Alert tone="danger" title="Could not reach the authentication service.">
      Two-factor authentication is still off. Try again in a moment.
    </Alert>
  )}
</div>
```

## Example — a third state

```tsx
import { RadioGroup, RadioGroupItem, Switch, Text } from '@misoto22/folio'

<div className="flex flex-col gap-8 sm:flex-row sm:gap-12">
  <div className="flex flex-col gap-2">
    <Text size="xs" tone="muted">
      Two states
    </Text>
    <label className="flex cursor-pointer items-center gap-3 text-sm text-(--ink-2)">
      <Switch defaultChecked /> Require review before merge
    </label>
  </div>
  <div className="flex flex-col gap-2">
    <Text size="xs" tone="muted">
      Three
    </Text>
    <RadioGroup defaultValue="inherit" aria-label="Require review before merge">
      <RadioGroupItem value="on">Always require review</RadioGroupItem>
      <RadioGroupItem value="off">Never require review</RadioGroupItem>
      <RadioGroupItem value="inherit">Inherit from the workspace</RadioGroupItem>
    </RadioGroup>
  </div>
</div>
```
