# Switch

An on-or-off control that takes effect at once: a filled track with a light thumb that slides to the on side.

Block: `smbk-switch`. Status: candidate. Figma: the Figma file, https://www.figma.com/community/file/1688324308358870630. Desktop and mobile: differ by presentation only.

## Options

| Axis | Values | Default | Meaning |
|---|---|---|---|
| `size` | `lg`, `md`, `sm` | `lg` | The track 24, 20 or 16 tall, with its thumb. The small thumb has no room for the tick or the cross. |

Parts: `smbk-switch__control`, `smbk-switch__track`, `smbk-switch__thumb`, `smbk-switch__on-icon`, `smbk-switch__off-icon`, `smbk-switch__label`

## Markup

```html
<label class="smbk-switch smbk-switch--size-lg">
  <input type="checkbox" role="switch" class="smbk-switch__control" checked=""/>
  <span class="smbk-switch__track" aria-hidden="true">
    <span class="smbk-switch__thumb"></span>
  </span>
  <span class="smbk-switch__label">Email notifications</span>
</label>
```

## React

```tsx
import { Switch } from './design-system/components/switch/switch.js';

<Switch size="lg" defaultChecked>Email notifications</Switch>
```

## Stylesheet

```css
/* smbk-switch — Switch. Figma "Switch" 4086:11238 (Size × State × Checked, with an Icon boolean).
 * The track is two thumbs wide plus its inset, so the thumb slides exactly
 * one thumb-width to the on side: its own width, so no distance is written.
 * The off track is filled, so the switch shows against any page without an edge.
 * The thumb stays light in both themes and may carry a tick when on and a cross when off.
 * --smbk-switch-thumb is the thumb's resting size at each size; the thumb's own width and height start from it and
 * stretch while it is pointed at or pressed (motion, below). */

.smbk-switch {
  --smbk-switch-thumb: var(--smbk-size-selection-md);
}

/* .smbk-switch__control: hidden from view, kept for screen readers and the keyboard, by the shared rule in visually-hidden/visually-hidden.css.
   The row, its type, the disabled colour, the focus ring on the track and the touch area are shared
   with the other choices, in choice/choice.css; this sheet draws the track. */

.smbk-switch__track {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  flex-shrink: 0;
  width: calc(2 * var(--smbk-size-selection-md) + 2 * var(--smbk-space-inset-2xs));
  height: var(--smbk-size-selection-lg);
  padding: var(--smbk-space-inset-2xs);
  border-radius: var(--smbk-radius-control-sm);
  background: var(--smbk-color-surface-track);
}

/* The thumb's two marks share its one cell; the state shows one of them. */
.smbk-switch__thumb {
  box-sizing: border-box;
  --smbk-switch-thumb-width: var(--smbk-switch-thumb);
  --smbk-switch-thumb-height: var(--smbk-switch-thumb);
  display: grid;
  place-items: center;
  width: var(--smbk-switch-thumb-width);
  height: var(--smbk-switch-thumb-height);
  border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-groove-bottom);
  border-radius: var(--smbk-radius-inset);
  background: var(--smbk-color-surface-thumb);
  box-shadow: var(--smbk-elevation-raised);
}

.smbk-switch__on-icon,
.smbk-switch__off-icon {
  grid-area: 1 / 1;
  color: var(--smbk-color-icon-on-thumb);
  font-size: var(--smbk-size-icon-xs);
}

.smbk-switch__on-icon,
.smbk-switch__control:checked + .smbk-switch__track .smbk-switch__off-icon {
  visibility: hidden;
}

.smbk-switch__control:checked + .smbk-switch__track .smbk-switch__on-icon {
  visibility: visible;
}

.smbk-switch__control:checked + .smbk-switch__track {
  background: var(--smbk-color-surface-brand);
}

/* Hover: an on track darkens its red; an off track stays as it is. */
@media (hover: hover) {
  .smbk-switch:hover .smbk-switch__control:enabled:checked + .smbk-switch__track {
    background: var(--smbk-color-action-brand-background-hover);
  }
}

/* On, the thumb's outer edge sits at the track's far end, two resting thumbs in: a thumb stretched wider than at
   rest grows toward the inside, so that edge stays where it is. */
.smbk-switch__control:checked + .smbk-switch__track .smbk-switch__thumb {
  translate: calc(2 * var(--smbk-switch-thumb) - var(--smbk-switch-thumb-width)) 0;
}

.smbk-switch__control:checked + .smbk-switch__track .smbk-switch__thumb:dir(rtl) {
  translate: calc(var(--smbk-switch-thumb-width) - 2 * var(--smbk-switch-thumb)) 0;
}

.smbk-switch__control:disabled + .smbk-switch__track {
  background: var(--smbk-color-surface-track-subtle);
}

.smbk-switch__control:disabled + .smbk-switch__track .smbk-switch__thumb {
  border-color: transparent;
  background: var(--smbk-color-surface-disabled);
  box-shadow: none;
}

.smbk-switch__control:disabled + .smbk-switch__track :is(.smbk-switch__on-icon, .smbk-switch__off-icon) {
  color: var(--smbk-color-icon-disabled);
}

/* ---- sizes ---- */

.smbk-switch--size-md .smbk-switch__track {
  width: calc(2 * var(--smbk-size-selection-sm) + 2 * var(--smbk-space-inset-2xs));
  height: var(--smbk-size-selection-md);
}

.smbk-switch--size-md {
  --smbk-switch-thumb: var(--smbk-size-selection-sm);
}

.smbk-switch--size-sm .smbk-switch__track {
  width: calc(2 * var(--smbk-size-12) + 2 * var(--smbk-space-inset-2xs));
  height: var(--smbk-size-selection-sm);
}

.smbk-switch--size-sm {
  --smbk-switch-thumb: var(--smbk-size-12);
}

/* The small thumb has no room for a mark. */
.smbk-switch--size-sm :is(.smbk-switch__on-icon, .smbk-switch__off-icon) {
  display: none;
}

/* ---- motion: the thumb slides across with the state purpose, the same way in both directions.
   The track's color changes at once, so a theme change never fades. With a mouse, pointing at the switch
   stretches the thumb a small step wider, into a pill; pressing stretches it a step more and squashes it as much,
   centred in the track (the small switch, whose thumb is tiny, squashes by the small step only). Off, the thumb
   grows from its start edge; on, toward the inside, so its outer edge stays at the track's end. The stretch
   moves with the state purpose. The box, the track and what is around the switch never move; a disabled
   switch and a touch screen do not stretch. Under reduced motion the thumb jumps and its size changes at once. ---- */

.smbk-switch__thumb {
  transition-property: translate, width, height;
  transition-duration: var(--smbk-motion-state-duration);
  transition-timing-function: var(--smbk-motion-state-easing);
}

@media (hover: hover) {
  .smbk-switch:hover .smbk-switch__control:enabled + .smbk-switch__track .smbk-switch__thumb {
    --smbk-switch-thumb-width: calc(var(--smbk-switch-thumb) + var(--smbk-space-2));
  }

  .smbk-switch:active .smbk-switch__control:enabled + .smbk-switch__track .smbk-switch__thumb {
    --smbk-switch-thumb-width: calc(var(--smbk-switch-thumb) + var(--smbk-space-4));
    --smbk-switch-thumb-height: calc(var(--smbk-switch-thumb) - var(--smbk-space-4));
  }

  .smbk-switch--size-sm:active .smbk-switch__control:enabled + .smbk-switch__track .smbk-switch__thumb {
    --smbk-switch-thumb-height: calc(var(--smbk-switch-thumb) - var(--smbk-space-2));
  }
}

@media (prefers-reduced-motion: reduce) {
  .smbk-switch__thumb {
    transition: none;
  }
}

/* ---- high contrast (forced colors) ---- */
/* In Windows high-contrast mode the browser drops fills and shadows; this puts the meaning back with the
   user's own colors. Nothing here applies outside that mode. */

@media (forced-colors: active) {
  /* Off: the fill is dropped, so the track takes an edge in the text color. */
  .smbk-switch__track {
    outline: var(--smbk-border-width-hairline) solid CanvasText;
  }

  /* On: the track filled with Highlight and edged in it, the thumb in HighlightText at the far end. */
  .smbk-switch__control:checked + .smbk-switch__track {
    background: Highlight;
    outline: var(--smbk-border-width-hairline) solid Highlight;
  }

  .smbk-switch__control:checked + .smbk-switch__track .smbk-switch__thumb {
    border-color: HighlightText;
    background: HighlightText;
  }

  .smbk-switch__control:checked + .smbk-switch__track .smbk-switch__on-icon {
    color: Highlight;
  }

  /* Disabled: the track keeps an edge, in GrayText; on, it is filled with GrayText. */
  .smbk-switch__control:disabled + .smbk-switch__track {
    outline: var(--smbk-border-width-hairline) solid GrayText;
  }

  .smbk-switch__control:disabled + .smbk-switch__track .smbk-switch__thumb {
    border-color: GrayText;
  }

  .smbk-switch__control:disabled:checked + .smbk-switch__track {
    background: GrayText;
  }

  .smbk-switch__control:disabled:checked + .smbk-switch__track .smbk-switch__thumb {
    border-color: Canvas;
    background: Canvas;
  }

  /* The focus ring again, since the rules above set the track's outline. */
  .smbk-switch__control:focus-visible + .smbk-switch__track {
    outline: var(--smbk-focus-ring-width) solid CanvasText;
    outline-offset: var(--smbk-focus-ring-offset);
  }
}
```

## Tokens it reads

```json
{
  "space": {
    "2": {
      "$type": "dimension",
      "$value": "2px",
      "$description": "Raw space step, 2 px. Only for fixed distances inside a component; spacing that follows density uses space/inset, stack or inline."
    },
    "4": {
      "$type": "dimension",
      "$value": "4px",
      "$description": "Raw space step, 4 px. Only for fixed distances inside a component; spacing that follows density uses space/inset, stack or inline."
    },
    "inline": {
      "sm": {
        "$type": "dimension",
        "$value": "{{space.6}}",
        "$description": "Gap between things side by side, small."
      }
    },
    "stack": {
      "md": {
        "$type": "dimension",
        "$value": "{{space.12}}",
        "$description": "Gap between things stacked one above another, medium."
      }
    },
    "inset": {
      "2xs": {
        "$type": "dimension",
        "$value": "{{space.2}}",
        "$description": "Padding inside a component, smallest."
      }
    }
  },
  "color": {
    "text": {
      "default": {
        "$type": "color",
        "$value": "{{color.grey.900}}",
        "$description": "Main text."
      },
      "disabled": {
        "$type": "color",
        "$value": "{{color.grey.500}}",
        "$description": "Text in unavailable controls."
      }
    },
    "border": {
      "focus": {
        "$type": "color",
        "$value": "{{color.red.600}}",
        "$description": "Keyboard focus ring on controls."
      },
      "groove-bottom": {
        "$type": "color",
        "$value": "{{color.alpha.white.100}}",
        "$description": "Lower line of an engraved divider or rim."
      }
    },
    "surface": {
      "track": {
        "$type": "color",
        "$value": "{{color.grey.500}}",
        "$description": "The filled track of a switch that is off: 3:1 against the page so the switch shows where it is (decision C)."
      },
      "thumb": {
        "$type": "color",
        "$value": "{{color.grey.50}}",
        "$description": "The knob of a switch: the same near-white in the light and the dark theme, so it reads as one knob on the red on-track (5.5:1) and on the grey off-track (3.5:1 light, 7.2:1 dark), above the 3:1 a control’s state needs. Used by: Switch. Falls back to color/surface/raised. Remove it if the thumb is to follow the theme again."
      },
      "brand": {
        "$type": "color",
        "$value": "{{color.red.600}}",
        "$description": "Brand-red surface: closers, highlights."
      },
      "track-subtle": {
        "$type": "color",
        "$value": "{{color.grey.300}}",
        "$description": "The empty part of a progress track: light enough that the fill stands out by lightness, not only by hue."
      },
      "disabled": {
        "$type": "color",
        "$value": "{{color.grey.200}}",
        "$description": "Surface of a disabled card or field."
      }
    },
    "icon": {
      "on-thumb": {
        "$type": "color",
        "$value": "{{color.grey.700}}",
        "$description": "The tick or cross inside a switch’s thumb: the same dark grey in both themes, 7.2:1 on color/surface/thumb. Used by: Switch (Icon on). Falls back to color/icon/subtle. Remove it together with color/surface/thumb."
      },
      "disabled": {
        "$type": "color",
        "$value": "{{color.grey.400}}",
        "$description": "Icons in unavailable controls."
      }
    },
    "action": {
      "brand": {
        "background-hover": {
          "$type": "color",
          "$value": "{{color.red.700}}",
          "$description": "Brand button and chip: fill on hover."
        }
      }
    }
  },
  "typography": {
    "body-sm": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "14px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      }
    }
  },
  "size": {
    "12": {
      "$type": "dimension",
      "$value": "12px",
      "$description": "Raw size step, 12 px. Only for fixed sizes inside a component; semantic sizes (size/control, icon, mark) point here."
    },
    "target": {
      "min": {
        "$type": "dimension",
        "$value": "{{size.24}}",
        "$description": "Smallest tap area allowed (24, WCAG 2.2)."
      },
      "comfortable": {
        "$type": "dimension",
        "$value": "{{size.44}}",
        "$description": "Comfortable touch target (44)."
      }
    },
    "selection": {
      "md": {
        "$type": "dimension",
        "$value": "{{size.20}}",
        "$description": "Checkbox and radio box, medium."
      },
      "lg": {
        "$type": "dimension",
        "$value": "{{size.24}}",
        "$description": "Checkbox and radio box, large."
      },
      "sm": {
        "$type": "dimension",
        "$value": "{{size.16}}",
        "$description": "Checkbox and radio box, small."
      }
    },
    "icon": {
      "xs": {
        "$type": "dimension",
        "$value": "{{size.12}}",
        "$description": "Icon box, extra small."
      }
    }
  },
  "focus": {
    "ring": {
      "width": {
        "$type": "dimension",
        "$value": "{{border-width.2}}",
        "$description": "Thickness of the focus ring."
      },
      "offset": {
        "$type": "dimension",
        "$value": "{{space.2}}",
        "$description": "Gap between a control and its focus ring."
      }
    }
  },
  "radius": {
    "control": {
      "sm": {
        "$type": "dimension",
        "$value": "{{radius.4}}",
        "$description": "Corners of controls, small."
      }
    },
    "inset": {
      "$type": "dimension",
      "$value": "{{radius.2}}",
      "$description": "Small corner inside a control: a checkbox mark, a track."
    }
  },
  "border-width": {
    "hairline": {
      "$type": "dimension",
      "$value": "{{border-width.1}}",
      "$description": "Thin lines: dividers and almost every edge."
    }
  },
  "elevation": {
    "raised": {
      "$type": "shadow",
      "$value": {
        "light": "0px 2px 8px 1px #373d431a, 0px 1px 2px 0px #373d431a",
        "dark": "0px 2px 8px 1px #00000026, 0px 1px 2px 0px #00000026"
      }
    }
  },
  "motion": {
    "state": {
      "duration": {
        "$type": "duration",
        "$value": "{{duration.200}}"
      },
      "easing": {
        "$type": "cubicBezier",
        "$value": "{{easing.cubic-in-out}}"
      }
    }
  }
}
```

## Purpose

An on-or-off setting that takes effect the moment it is flipped, like a light switch. The thumb's position says the state; the brand fill says it is on, the gray fill that it is off. A checkbox is for a choice saved later; a switch is for a change made now.

## Anatomy

- The block, a label element holding everything.
- The control part, the real checkbox with the switch role, visually hidden.
- The track part, the rounded channel, filled gray when off and red when on.
- The thumb part, the raised light square that sits at one end or the other. It stays light in both themes.
- The on-icon and off-icon parts, optional: a tick on the thumb when on and a cross when off, on the lg and md sizes.
- The label part, the text.

## When to use

- Settings and preferences that apply immediately.
- Turning a feature on or off in a panel.

## When not to use

- Anything submitted with a form. Use a [checkbox](checkbox.md).
- Choosing between two named options. Use the [segmented control](segmented-control.md), which shows both names.

## Behavior

Flipping slides the thumb to the other end with the state motion, the same in both directions, and swaps the track fill at once, so a theme change never fades it. Under reduced motion the thumb jumps. Under the pointer, an on track darkens its red; an off track stays as it is. With a mouse, pointing at the switch stretches the thumb a little wider, like a pill, and pressing it stretches the thumb further and squashes it, so the switch answers the press before it flips. Off, the thumb grows from its start; on, it grows inward, so its outer edge stays at the end of the track. The switch itself and everything around it never move. A disabled switch and a touch screen do not stretch, and under reduced motion the stretch changes at once. Disabled turns the track a faint gray, the thumb the disabled surface without its edge or lift.

With `icon`, the thumb carries a tick when on and a cross when off. The small thumb has no room for them, so the small size never shows them.

Three sizes: lg, a 24 track; md, 20; sm, 16. At every size the whole row is at least 24 tall.

On a touch screen the row keeps its height and takes an invisible press area out to the comfortable touch target; between switches stacked one under another it reaches only halfway into the gap.

## Accessibility
### What it does for you

- The control carries the switch role, so it is announced as a switch with on and off, and Space flips it.
- The label element names it; the text is required.
- The focus ring draws around the track.
- State is shown by position and fill, not by color alone.
- The tick and the cross are hidden from assistive technology, which hears the state from the control.
- On a touch screen the row keeps its height and takes an invisible press area out to the comfortable touch target.

### What you must do

- Give every switch label text, which names it.

### How to test

- Tab to the switch: the focus ring shows around the track, in both themes and in a forced-color mode. Press Space: it flips.
- With a screen reader, it is read as a switch by its label, on or off.
- On a touch screen, a press just outside the track still flips it.
- With text at 200% and the page 320 pixels wide, the label stays whole beside the track.

### Keyboard

| Key | What it does |
|---|---|
| Tab | Moves focus to the switch. A disabled switch is skipped. |
| Space | Flips the switch. |

## Usage rules

### Do

- Label the setting, not the action: "Notifications", not "Turn on notifications".

### Don't

- Ask for confirmation after a switch. If a change needs confirming, it is not a switch.
