Switch

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.

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

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.

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.

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.

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.

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.

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

Keyboard

Key Action
Space Turns the focused switch on or off.

API

SwitchProps

NameTypeDefaultDescription
labelReactNode—Label to the right of the switch.

Shown in: Configurator, On and off, 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.

Shown in: Configurator, States and sizes

invalidboolean—Marks the switch as invalid. FormField sets it itself as soon as it carries an error - by hand only necessary without FormField.

Shown in: Configurator, With an error

refRef<HTMLInputElement>—Receives the component's root element. from RefProp

Shown in: A row that holds still (Sizes), An anchored surface (Popover), Take the width of the trigger (Popover) and 2 more

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

Known limits

What umriss deliberately does not build, and why: ADR-0032.

Every page of @umriss-ui/core

Getting started

Customising

Layout

Typography

Actions

Forms

Choosing

Dates and times

Feedback

Overlays

Navigation

Data display

API index