v3.1.0
Checkbox

Binary selection control: a 24 box carrying a check glyph when selected, wrapped in a 48 touch target. Used in forms, filters, settings, and multi-select lists.

3.1.0 3.1.0 -- --

Overview

Checkbox turns a single option on or off, or picks any number of items from a set. One size, one shape — every form it ships in is below.

Standard
Selected
Disabled
Selected disabled

States

Four states, matching the Figma variant matrix State x Disabled. There is no indeterminate state in the contract.

Standard
1
Selected
2
Disabled
3
Selected disabled
4
  • 1
    Standard var(--text-and-icon-secondary) — the border, on a transparent box.
  • 2
    Selected var(--text-and-icon-primary) — the fill.var(--text-and-icon-inverse-primary) — the glyph.
  • 3
    Disabled var(--text-and-icon-disabled) — the border. A token swap, never opacity.
  • 4
    Selected disabled var(--text-and-icon-disabled) — the fill.var(--text-and-icon-inverse-primary) — the glyph, still visible.

Anatomy

Three nested layers, mirroring the Figma component chain: the touch target wraps the box, the box carries the glyph.

.checkbox — 48 --no-safezone — 24
1
2
3
  • 1
    .checkbox var(--sp-s48) — the safe zone. The touch target and the focusable element. Required.
  • 2
    .checkbox__box var(--sp-s24) — the visible box, centred in the safe zone. Required.
  • 3
    .checkbox--no-safezone var(--sp-s24) — the wrapper collapses onto the box. For a parent that already owns the hit area and the semantics.

Layout

Every dimension comes from the SP scale. The gap between the target edge and the box is geometry, not padding — the box is centred inside the target, which produces 12 on each side.

1
2
.checkbox — 48 target, 24 box centred
3
  • General var(--sp-s48) — touch target (WCAG 2.5.5).var(--sp-s24) — visible box.var(--sp-s8) — roundness.var(--sp-s2) — border.var(--sp-s0) — padding; flex centring produces the 12 inset.
  • 1
    Box 24 × 24, centred in the target. Its border colour matches the fill when selected, so the outer size never shifts between states.
  • 2
    Target 48 × 48 — the focusable, clickable element. The 12 inset on each side is centring, not padding: padding: var(--sp-s0).
  • 3
    Border var(--sp-s2), drawn inside the box (box-sizing: border-box), so the 24 outer size holds.

Animation

One token carries the whole state change, in both directions. Toggle either control to see it.

Unselected Selected
1
2
  • 1
    The box var(--transforming-state-default) — 200ms on slow-ease-out, the same token both ways.background-color and border-color. The border matches the fill when selected, so the 24 outer size never shifts mid-transition.
  • 2
    The glyph opacity — 0 to 1 on the same token. It never scales or moves, so the change stays on the compositor.Catalogued in motion-rules.md §6.6 as Transforming/State/Default.
  • Reduced motion The token drops to 0ms and the change is instant. Zeroed at the token in motion.css, so the component carries no override of its own.

Usage

Checkbox is for independent binary choices. The shape of the question decides the control, not how the options are worded: if the answers are independent it is Checkbox, if exactly one of them can hold it is Radio, and if the change applies the moment it is made it is Switch.

  • 1
    Use for One option on or off, or any number out of a set — including none of them.
  • 2
    Not for Exactly one of several — that is Radio. An immediate on/off that applies without confirmation — that is Switch.
Child seat
Pet friendly
Extra luggage
✓ Independent options — any number on at once
Card
Cash
Corporate account
✕ One choice out of several — that is Radio

Accessibility

Tab here, then press Space Unavailable
1
2
3
  • 1
    The s48 target role="checkbox" and aria-checked, mirroring the visual state. Required, not optional: the DS markup is a <button> so the box can be styled, and a button announces as a button and exposes no value at all.aria-labelledby when a label is on screen, aria-label only when none is.:focus-visible — var(--sp-s2) in var(--text-and-icon-primary) at radius var(--sp-s12), inset. Inset so the ring stays inside the target and never collides with a neighbour in a dense list.aria-checked and data-ds-state update in one handler, so the accessible value and the analytics value cannot drift apart.
  • 2
    The s24 box never takes focus Focusing it would give a 24 x 24 hit area and fail WCAG 2.5.5. The minimum is 24, 44 is recommended for pointer input, and the DS uses 48.
  • 3
    Disabled The two platforms differ, and the difference is deliberate. TalkBack appends "Disabled", skips the control in swipe navigation and ignores a double tap. VoiceOver appends "Dimmed" and keeps it in the rotor, so its state stays discoverable.Selected disabled still reads its value. "Checked, Disabled" — so a person knows which state is frozen, not just that something is unavailable.
  • Keyboard Space toggles. Enter does nothing — it stays free to submit the surrounding form.A real <button> synthesizes a click on Enter, so a bare click handler would toggle on it. The handler flags Enter on keydown and swallows the click it produces. A native input needs none of this — the guard is the cost of styling the box.
  • Never colour alone The check glyph is the non-colour indicator that separates selected from unselected. Which matters because var(--text-and-icon-disabled) sits below the 3:1 floor on purpose — a disabled control is exempt from WCAG 1.4.11, so disabled is never the only carrier of information.