# Switch

> Package `@umriss-ui/core`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/core/switch/>. Every page in one line: [llms.txt](https://romanhaendler.github.io/umriss-ui/core/llms.txt); every page in full: [llms-full.txt](https://romanhaendler.github.io/umriss-ui/core/llms-full.txt).

On or off, taking effect at once (toggle): paging at night, autoscaling, a feature for one service. A screen reader announces it as a switch, on or off.

```ts
import { Switch } from "@umriss-ui/core";
```

Demo page: https://romanhaendler.github.io/umriss-ui/core/switch/

## Examples

### On and off

Use a switch for a setting that takes effect the moment it flips; Space and a click on the label toggle it.

```tsx
import { Switch } from "@umriss-ui/core";

export default function OnAndOff() {
  return <Switch label="Page me at night" defaultChecked />;
}
```

### States and sizes

Both sizes, off and on, each also `disabled`; an `error` on the surrounding `FormField` marks the switch invalid.

```tsx
import { FormField, Grid, Stack, Switch, Text } from "@umriss-ui/core";

export default function StatesAndSizes() {
  return (
    <Grid minItemWidth="220px" gap={5}>
      <Stack gap={3}>
        <Text size="xs" tone="muted">
          Medium
        </Text>
        <Switch label="Status page" />
        <Switch label="Error budget alerts" defaultChecked />
        <Switch label="Public API" disabled />
        <Switch label="Audit log" disabled defaultChecked />
      </Stack>
      <Stack gap={3}>
        <Text size="xs" tone="muted">
          Small
        </Text>
        <Switch size="sm" label="Status page" />
        <Switch size="sm" label="Error budget alerts" defaultChecked />
        <Switch size="sm" label="Public API" disabled />
        <Switch size="sm" label="Audit log" disabled defaultChecked />
      </Stack>
      <Stack gap={3}>
        <Text size="xs" tone="muted">
          Invalid, and a long label
        </Text>
        <FormField label="Paging" error="A tier 1 service has to page its on-call engineer.">
          <Switch label="Page on-call" />
        </FormField>
        <Switch label="Post every deployment of this service to the team channel, including the automatic rollbacks" defaultChecked />
      </Stack>
    </Grid>
  );
}
```

### With an error

A `FormField` with an `error` marks the switch invalid by itself. Without one, set `invalid` and tie your message to the switch with `aria-describedby`.

```tsx
import { useId, useState } from "react";
import { Stack, Switch, Text } from "@umriss-ui/core";

export default function WithAnError() {
  const [paging, setPaging] = useState(false);
  const messageId = useId();
  const error = paging ? undefined : "A tier 1 service has to page its on-call engineer.";

  return (
    <Stack gap={1}>
      <Switch
        label="Page on-call, Checkout (tier 1)"
        checked={paging}
        onChange={(event) => setPaging(event.target.checked)}
        invalid={error !== undefined}
        aria-describedby={error ? messageId : undefined}
      />
      {error && (
        <Text id={messageId} size="xs" style={{ color: "var(--u-color-danger-text)" }}>
          {error}
        </Text>
      )}
    </Stack>
  );
}
```

### Let the switch decide what follows

Control it with `checked` and `onChange` when other fields depend on it; here autoscaling takes the instance count away.

```tsx
import { useState } from "react";
import { FormField, NumberInput, Stack, Switch } from "@umriss-ui/core";

export default function LetTheSwitchDecide() {
  const [autoscaling, setAutoscaling] = useState(true);
  const [instances, setInstances] = useState<number | null>(6);

  return (
    <Stack gap={4} style={{ maxWidth: 320 }}>
      <Switch label="Autoscaling" checked={autoscaling} onChange={(event) => setAutoscaling(event.target.checked)} />
      <FormField label="Instances" hint={autoscaling ? "Set by autoscaling, between 2 and 24." : "Fixed by hand."}>
        <NumberInput value={instances} onChange={setInstances} min={1} max={24} decimals={0} disabled={autoscaling} />
      </FormField>
    </Stack>
  );
}
```

### Settings of a service

Give each switch its sentence through `aria-describedby`, and say why when one is `disabled` for now.

```tsx
import { useId, useState } from "react";
import { Badge, Card, CardBody, CardHeader, Divider, Stack, Switch, Text } from "@umriss-ui/core";

interface Setting {
  id: string;
  label: string;
  description: string;
  lockedDuringIncident?: boolean;
}

const SETTINGS: Setting[] = [
  { id: "deploys", label: "Deployments", description: "Lets the pipeline release to production." },
  { id: "canary", label: "Canary releases", description: "Sends a new release to 5 % of traffic for 15 minutes first." },
  { id: "flags", label: "Feature flag changes", description: "Only while no incident is open on this service.", lockedDuringIncident: true },
  { id: "page", label: "Page on-call", description: "Pages the primary when the latency objective is breached." },
  { id: "digest", label: "Weekly digest", description: "Mails the team the week's latency and error budget." },
];

export default function SettingsOfAService() {
  const [on, setOn] = useState<Record<string, boolean>>({ deploys: true, canary: true, page: true });
  const incidentOpen = true;
  const base = useId();
  const count = SETTINGS.filter((s) => on[s.id]).length;

  return (
    <Card style={{ maxWidth: 520 }}>
      <CardHeader eyebrow="Checkout · Payments" title="Service settings" actions={<Badge pill>{count} on</Badge>} />
      <CardBody>
        <Stack gap={3}>
          {SETTINGS.map((s, i) => {
            const locked = s.lockedDuringIncident === true && incidentOpen;
            return (
              <Stack key={s.id} gap={3}>
                {i > 0 && <Divider />}
                <Stack gap={1}>
                  <Switch
                    label={s.label}
                    checked={on[s.id] === true}
                    disabled={locked}
                    aria-describedby={`${base}-${s.id}`}
                    onChange={(event) => setOn((previous) => ({ ...previous, [s.id]: event.target.checked }))}
                  />
                  <Text id={`${base}-${s.id}`} size="xs" tone="muted" style={{ paddingLeft: 36 }}>
                    {locked ? "Locked while INC-1048 is open. " : ""}
                    {s.description}
                  </Text>
                </Stack>
              </Stack>
            );
          })}
        </Stack>
      </CardBody>
    </Card>
  );
}
```

## When to use something else

- A choice that only counts after a Save button → Checkbox

## Keyboard

| Key | Action |
|---|---|
| `Space` | Turns the focused switch on or off. |

## API

### `SwitchProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `label` | `ReactNode` | — | Label to the right of the switch.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/switch/#configurator-switch), [On and off](https://romanhaendler.github.io/umriss-ui/core/switch/#on-and-off), [States and sizes](https://romanhaendler.github.io/umriss-ui/core/switch/#states-and-sizes) and 13 more |
| `size` | `"sm" \| "md"` | the size of a `ControlSizeProvider` around it, else `"md"` | `sm` for a toolbar and dense forms, `md` otherwise.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/switch/#configurator-switch), [States and sizes](https://romanhaendler.github.io/umriss-ui/core/switch/#states-and-sizes) |
| `invalid` | `boolean` | — | Marks the switch as invalid. `FormField` sets it itself as soon as it carries an `error` - by hand only necessary without `FormField`.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/switch/#configurator-switch), [With an error](https://romanhaendler.github.io/umriss-ui/core/switch/#with-an-error) |
| `ref` | `Ref<HTMLInputElement>` | — | Receives the component's root element. From `RefProp`.<br>Shown in: [A row that holds still (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#a-row-that-holds-still), [An anchored surface (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#anchored-surface), [Take the width of the trigger (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#width-from-anchor) and 2 more |

Also takes every attribute of `<input>` – without `role`, `size`, `type`.

## Known limits

- No pending or loading state; while your application applies the change, disable the switch or say so beside it.

What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
