Switch

A switch turns a setting on or off, and takes effect straight away. It can also be disabled, on or off.

import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";

export default function SwitchDemo() {
  return (
    <>
      <Label>
        <Switch defaultChecked />
        Wi-Fi
      </Label>
      <Label>
        <Switch />
        Bluetooth
      </Label>
      <Label>
        <Switch disabled />
        Location
      </Label>
      <Label>
        <Switch disabled defaultChecked />
        Sync
      </Label>
    </>
  );
}

The code

The component is a single file, read from the repository when the site is built. Press Show code to see all of it.

import type { ComponentProps } from "react";
import { Switch as BaseSwitch } from "@base-ui/react/switch";
import { cx } from "@/lib/cx";
import { focusRing } from "@/lib/styles";

// On is the accent; off is the strong border color. The thumb is light on the off track and dark
// on the green one. A disabled switch goes flat and grey but still shows which way it is set.
const root = [
  "inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full bg-border-strong p-0.5",
  "transition-colors duration-150",
  focusRing,
  "not-data-[disabled]:data-[checked]:bg-accent",
  "data-[disabled]:cursor-not-allowed data-[disabled]:bg-surface data-[disabled]:ring-1 data-[disabled]:ring-border data-[disabled]:ring-inset",
].join(" ");

const thumb = [
  "block size-5 rounded-full bg-text",
  "transition-[translate,background-color] duration-150",
  "data-[checked]:translate-x-5 not-data-[disabled]:data-[checked]:bg-bg",
  "data-[disabled]:bg-text-secondary",
].join(" ");

export function Switch({
  className,
  ...props
}: Omit<ComponentProps<typeof BaseSwitch.Root>, "className"> & { className?: string }) {
  return (
    <BaseSwitch.Root className={cx(root, className)} {...props}>
      <BaseSwitch.Thumb className={thumb} />
    </BaseSwitch.Root>
  );
}

Props

PropTypeDefaultWhat it does
defaultCheckedbooleanfalseWhether it starts on, when you let it manage its own state.
checkedboolean—Whether it is on, when you control it yourself.
onCheckedChangefunction—Called with the new value when the switch is flipped.
disabledbooleanfalseTurns the switch off. It keeps showing which way it was set.
classNamestring—Extra classes, added after the switch's own.

Every other prop, such as name, goes to the Base UI Switch underneath.

The thinking

A switch and a checkbox look alike but promise different things, and the difference matters to the person using them.

  • Immediate or later: Use a switch for a setting that applies the moment it flips, like Wi-Fi. Use a checkbox for a choice that is submitted later with a form.
  • Built on Base UI: It is announced as a switch, with the on or off state, and flips with the keyboard.
  • Green means on: The accent fills the track when the switch is on, so the state reads at a glance.

Interface design

  • Size: A pill 44px wide and 24px tall, with a 20px thumb.
  • Off: The strong border color for the track and a light thumb, 8.2:1 against the track.
  • On: An accent track and a dark thumb, 14.6:1 against it.
  • Disabled: A flat track with a hairline edge and a grey thumb. The thumb still sits left or right, so you can see how it is set.

Interaction

  • Flip it: Click it, click its label's text, or press Space when it has focus.
  • Motion: The thumb slides across and the track changes color over 150ms.
  • Focus: Keyboard focus draws a 2px outline, 2px away from the pill.
  • Disabled: It doesn't flip, and shows a not-allowed cursor.

Empathy

  • Name the setting, not the state: Label it Wi-Fi, not On. The switch itself already says whether it is on.
  • Announced: A screen reader says the label, that it is a switch, and whether it is on or off.
  • Don't rely on color: The thumb's position shows the state as well as the track color.
  • Target size: 44px wide and 24px tall, which meets the 24px minimum of WCAG 2.2 AA.