Switch

A binary toggle painted as a sliding pill. Markup is a regular <input type="checkbox"> with the .switch class so it keeps the checkbox keyboard contract; the thumb is a positioned ::before that translates on :checked. Add role="switch" and aria-checked so AT announces the switch idiom rather than "checkbox".

Live

On, off, disabled

<div style="display: flex; flex-direction: column; gap: 0.5rem;">
<label style="display: inline-flex; align-items: center; gap: 0.5rem;">
  <input type="checkbox" class="switch" role="switch" aria-checked="true" checked /> Notifications on
</label>
<label style="display: inline-flex; align-items: center; gap: 0.5rem;">
  <input type="checkbox" class="switch" role="switch" aria-checked="false" /> Notifications off
</label>
<label style="display: inline-flex; align-items: center; gap: 0.5rem;">
  <input type="checkbox" class="switch" role="switch" disabled /> Disabled
</label>
</div>
@use '@adnap/krysalicss' with (
  $feature-list: ('base-reset', 'base-global', 'element-switch')
);
Playground

Markup

markup
<label>
  <input type="checkbox" class="switch" checked> Notifications on
</label>
<label>
  <input type="checkbox" class="switch"> Notifications off
</label>
<label>
  <input type="checkbox" class="switch" disabled> Disabled
</label>

When to use

  • A single binary setting whose change is committed immediately (notifications on/off, dark mode).
  • For deferred-commit choices submitted with a form, prefer Checkbox — the checkbox idiom signals "pending" rather than "live".
  • For one-of-many exclusive choices, use Radio.

Variables

VariableDefaultNotes
$selector'input[type="checkbox"].switch'Class opt-in. The checkbox plugin excludes .switch so the two rule sets don't conflict.
$height1.25remTrack height. Thumb is sized off this minus inset and border.
$width2.25remTrack width. Thumb travel = width - height.
$thumb-inset2pxGap between thumb and track edge.
$border-width$global-border-width (1px)Track border.
$focus-outline-width$global-focus-outline-width (2px)Focus-visible ring width.
$focus-outline-style$global-focus-outline-style (solid)Focus ring style.
$focus-outline-offset$global-focus-outline-offset (2px)Gap between border and ring.
$disabled-opacity$global-disabled-opacity (0.6)Routed through controls.disabledControl().
$border-mix$global-border-mix (50%)Track border tint when off.
$track-mix50%Off-state track fill: color-mix(in srgb, currentColor 50%, transparent). Higher = denser track when off.
$transition-duration$global-transition-duration (120ms)Thumb translation + colour swap easing. Honoured under prefers-reduced-motion: reduce (transition zeroed).

Override example

app.scss
@use '@adnap/krysalicss/element/switch' with (
  $width: 2.75rem,
  $height: 1.5rem,
  $track-mix: 35%,
);

Tokens consumed

TokenUsed for
--kc-primary-bg / --kc-primary-fgChecked-state track fill, border, and thumb fill.
--kc-fgOff-state thumb fill (highest available contrast against the track tint).
--kc-focus-ringPainted on :focus-visible. Falls back to --kc-fg then currentColor.

Accessibility

  • Markup is still a native checkbox, so keyboard activation (Space), label-click toggling, and form-submission semantics ship for free.
  • Add role="switch" and bind aria-checked to the input's checked state so screen readers announce "switch, on/off" rather than "checkbox, checked/unchecked".
  • State is conveyed by both fill colour and thumb position, so the cue isn't colour-only (WCAG 1.4.1).
  • The transition is short ($global-transition-duration, 120ms) and disabled under prefers-reduced-motion: reduce.
  • Forced-colors mode remaps fills to ButtonFace / CanvasText off, Highlight / HighlightText on so the toggle still reads under Windows High Contrast.
  • The 1.25rem × 2.25rem track is below the WCAG 2.5.5 (44 × 44 px) touch-target floor. Wrap the input in a <label> whose hit-area meets the floor.

See also

  • Checkbox — same <input type="checkbox"> markup without the .switch class.
  • Forms overview — full form-control roster.
  • Field wrapper — pairs every form control with label + help text.