Navigation

v0.0.4
Forms

Switch

A pearl thumb that slides onto ink for settings that apply at once.

On this page

A switch is a well whose pearl thumb slides onto ink. Use .switch on a native checkbox with role="switch" for settings that take effect immediately; for choices submitted with a form, a checkbox is clearer.

Switch

<label class="check"><input type="checkbox" role="switch" class="switch" checked>Email notifications</label>

#Sizes

Sizes

<input type="checkbox" role="switch" class="switch switch-sm" aria-label="Small" checked><input type="checkbox" role="switch" class="switch" aria-label="Medium" checked><input type="checkbox" role="switch" class="switch switch-lg" aria-label="Large" checked>

#In a settings list

Settings list

<div class="sheet flex flex-col p(0)">  <label class="check justify-between gap-lg px-lg py-md">    <span>Automatic updates</span>    <input type="checkbox" role="switch" class="switch" checked>  </label>  <label class="check justify-between gap-lg px-lg py-md bt(1) bt-solid bt-line-hair">    <span>Usage analytics</span>    <input type="checkbox" role="switch" class="switch">  </label></div>

#Disabled

Disabled

<input type="checkbox" role="switch" class="switch" aria-label="Off" disabled><input type="checkbox" role="switch" class="switch" aria-label="On" checked disabled>

#Accessibility

  • Keep role="switch" so assistive technology announces on and off rather than checked.
  • Every switch needs a label: a wrapping <label>, a <label for> or aria-label.
  • The input stays focusable and toggles with Space, as a native control does.

#Classes

ClassDescription
switchA native checkbox with role="switch": a track 22px tall with a thumb 3px inside it. On a touch screen its target is 44px around the same track, and where words run from the right the thumb travels the other way
switch-smAn 18px track; the default is 22px. With switch
switch-lgA 26px track; the default is 22px. With switch

#Source

@master/ui/components/switch.css
/* * Switch: a native checkbox with role="switch", a well whose pearl thumb slides onto ink. * Thumb and track are concentric: the thumb keeps an even 3px from every edge. */@layer components {  .switch {    --switch-width: 2.25rem;    --switch-height: 1.375rem;    --switch-inset: 3px;    --switch-thumb: calc(var(--switch-height) - var(--switch-inset) * 2);    /* Pressing stretches the thumb toward its travel, like a finger on a physical toggle. */    --switch-stretch: 0px;    position: relative;    flex: none;    width: var(--switch-width);    height: var(--switch-height);    margin: max(0px, calc((1lh - var(--switch-height)) / 2)) 0;    padding: 0;    border: 0;    border-radius: 999px;    background: var(--color-surface-inset);    /* The unchecked track doubles its half-pixel contour so it reads on a white sheet. */    box-shadow: var(--shadow-pressed), inset 0 0 0 .5px var(--color-line-contour);    vertical-align: middle;    appearance: none;    cursor: pointer;    touch-action: manipulation;    -webkit-tap-highlight-color: transparent;    transition-property: background-color, box-shadow;    transition-duration: var(--duration-base);    transition-timing-function: var(--easing-glide);    &::before {      content: '';      position: absolute;      top: var(--switch-inset);      inset-inline-start: var(--switch-inset);      width: calc(var(--switch-thumb) + var(--switch-stretch));      height: var(--switch-thumb);      border-radius: 999px;      @apply --surface-thumb;      box-shadow: 0 0 0 .5px var(--color-line-strong), 0 1px 2px -.5px var(--color-shade-1), 0 2px 4px -1px var(--color-shade-2);      transition-property: translate, width;      transition-duration: var(--duration-base);      transition-timing-function: var(--easing-glide);    }    &:active:not(:disabled) {      --switch-stretch: 3px;    }    &:checked {      background: var(--color-surface-inverse);      box-shadow: var(--shadow-ink-well);      &::before {        box-shadow: var(--shadow-thumb-on-ink);        translate: calc(var(--switch-width) - var(--switch-height) - var(--switch-stretch)) 0;        background: linear-gradient(180deg, var(--color-thumb-on-high), var(--color-thumb-on-low));      }    }    /* Where words run from the right, on is at the left: the thumb travels the other way. */    &:checked:dir(rtl)::before {      translate: calc(-1 * (var(--switch-width) - var(--switch-height) - var(--switch-stretch))) 0;    }    &:focus-visible {      @apply --focus-ring;    }    &:disabled {      opacity: .5;      cursor: not-allowed;    }    /* On touch the track keeps its size and its target grows to 44px around it. */    @media (pointer: coarse) {      &::after {        content: '';        position: absolute;        inset: calc((var(--switch-height) - 2.75rem) / 2) min(0px, calc((var(--switch-width) - 2.75rem) / 2));      }    }    &.switch-sm {      --switch-width: 1.75rem;      --switch-height: 1.125rem;      --switch-inset: 2px;    }    &.switch-lg {      --switch-width: 2.75rem;      --switch-height: 1.625rem;    }    @media (prefers-reduced-motion: reduce) {      &:active:not(:disabled) {        --switch-stretch: 0px;      }    }    @media (forced-colors: active) {      forced-color-adjust: none;      background: Field;      box-shadow: inset 0 0 0 1px FieldText;      &::before {        background: FieldText;        box-shadow: none;      }      &:checked {        background: Highlight;        box-shadow: none;        &::before {          background: HighlightText;        }      }      &:disabled {        opacity: 1;        box-shadow: inset 0 0 0 1px GrayText;        &::before {          background: GrayText;        }      }      &:focus-visible {        outline-color: Highlight;      }    }  }}