macbetter

Buttons and controls

Switch

Turn a setting on or off.

import { Switch } from "macbetter/switch"

export default function Example() {
  return (
    <>
      <Switch aria-label="Wi-Fi" defaultChecked />
      <Switch aria-label="Bluetooth" />
    </>
  )
}

When to use it

Use a switch for a setting that takes effect the moment it changes, like turning on Wi-Fi or Low Power Mode. If the change only applies when someone presses Save or OK, use a Checkbox instead.

  • Name what the switch controls, not its state: "Bluetooth", not "Turn Bluetooth on".
  • On macOS, prefer a checkbox for most settings in a window. Use a switch for a setting that controls a whole group or a major feature, as System Settings does.
  • On iOS and iPadOS, a switch is the standard way to show a binary setting in a list row.

Usage

import { Switch } from "macbetter/switch"

In a settings row

Put the switch in a Field with a FieldLabel, and the label names it and toggles it when clicked. A FieldDescription is read with it.

Reduces background activity until you charge your device.
import { Field, FieldDescription, FieldLabel } from "macbetter/field"
import { Switch } from "macbetter/switch"

// The label and description on the leading side, the switch on the trailing edge.
const row = { display: "grid", gridTemplateColumns: "1fr auto", alignItems: "center", gap: "2px 16px" }
const title = { gridColumn: 1, padding: 0, color: "var(--mb-label)", font: "var(--mb-text-body)" }

export default function Example() {
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 18, width: "100%", maxWidth: 420 }}>
      <Field style={row}>
        <FieldLabel style={title}>Low Power Mode</FieldLabel>
        <Switch style={{ gridColumn: 2, gridRow: "1 / span 2" }} />
        <FieldDescription style={{ gridColumn: 1, padding: 0 }}>
          Reduces background activity until you charge your device.
        </FieldDescription>
      </Field>
      <Field style={row}>
        <FieldLabel style={title}>Show Battery Percentage</FieldLabel>
        <Switch style={{ gridColumn: 2 }} defaultChecked />
      </Field>
    </div>
  )
}

Sizes

size="sm" is macOS's mini switch, for dense inspectors and tables. On touch, it's a smaller switch for compact rows.

import { Switch } from "macbetter/switch"

export default function Example() {
  return (
    <>
      <Switch size="sm" aria-label="Snap to Grid" defaultChecked />
      <Switch size="md" aria-label="Show Rulers" defaultChecked />
    </>
  )
}

Disabled

A disabled switch keeps its state visible but can't be focused or changed. Say why nearby, when it isn't obvious.

import { Switch } from "macbetter/switch"

export default function Example() {
  return (
    <>
      <Switch aria-label="iCloud Drive" disabled defaultChecked />
      <Switch aria-label="Desktop and Documents Folders" disabled />
    </>
  )
}

Platform behavior

  • macOS. A 38 × 22 point capsule. The thumb stretches toward where it's going while it's held, then springs across.
  • iPadOS and iOS. The 51 × 31 point UISwitch with a wider stretch, so it feels right under a thumb.
  • Every platform. On is green (--mb-green), as it is everywhere in Apple's platforms, whatever the theme's accent. The hit area is extended to the platform minimum.

Accessibility

  • Renders a button with the switch role and aria-checked, plus a hidden native checkbox for forms.
  • Space toggles it; Enter submits its form and never toggles it.
  • Name it with a FieldLabel or aria-label. The label stays the same in both states.
  • The on state is a fill, not only a color: the thumb also moves to the trailing side.

API

PropTypeDefaultDescription
size"sm" | "md""md"md is the standard switch; sm is the mini switch.
checkedboolean—Whether it's on. Pass it to control the switch.
defaultCheckedbooleanfalseWhether it's on at first, when uncontrolled.
onCheckedChange(checked, details) => void—Runs when it wants to change.
disabledbooleanfalseCan't be focused or changed, and isn't submitted.
readOnlybooleanfalseFocusable and submitted, but doesn't change.
namestring—The name it submits under.
childrenReactNode—Replaces the built-in thumb, if you need a custom one.

SwitchThumb

The thumb Switch renders for you. Render it yourself, as the switch's child, only to put something inside it or give it your own class. It takes the props of ui-sdk's SwitchThumb.

Data attributes

AttributeValues
data-sizesm, md
data-checked / data-uncheckedPresent for the current state
data-disabled, data-readonly, data-invalidPresent in those states

CSS variables

VariablemacOSiOSWhat it sets
--mb-switch-width38px51pxTrack width
--mb-switch-height22px31pxTrack height
--mb-switch-width-sm26px42pxSmall track width
--mb-switch-height-sm15px26pxSmall track height
--mb-switch-thumb-stretch3px6pxHow far the thumb stretches while held
--mb-switch-on--mb-green--mb-greenThe on color, set on the switch