Checkbox

Atom

A binary selection control with three visual states (unchecked / checked / indeterminate) and three interaction states (default / hover / disabled). Built on a native <input type="checkbox"> — fully keyboard-accessible with no JS required for state.

When to use

✅ Do
  • Use for multi-select — when the user can pick zero or more options from a list
  • Use for binary settings that take effect only after a Save or Submit action
  • Use the indeterminate state for a "select all" parent that has mixed children
  • Always pair with a visible label that describes what checking the box does
  • Group related checkboxes together with a clear group heading
❌ Don't
  • Use when only one option can be selected — use Radio Button instead
  • Use for a setting that takes effect immediately — use Toggle Switch instead
  • Use without a label — a bare checkbox with no description is inaccessible
  • Use more than 8 checkboxes in a flat list — consider grouping or a Dropdown
  • Use to trigger an action directly — use a Button

DigiLawyer Patterns

Product-specific conventions used across DigiLawyer interfaces.

Checkbox first, label second — always
The checkbox control always appears on the left. The label text always follows to the right. Never place the label before the checkbox. This is consistent across every form and list in the DigiLawyer product.
✅ Do — checkbox then label
❌ Don't — label before checkbox

Anatomy

A Checkbox is composed of a hidden native input, a visible box, and an optional label. The box and glyph are CSS-only — no SVG injection at runtime.

  1. 1Hidden input.checkbox-input. Visually hidden but focusable. Drives all states via CSS :checked, :disabled, :focus-visible using the ~ sibling selector.
  2. 2Box.checkbox-box. The visible square. Background and border change per state via sibling CSS.
  3. 3Check glyph — Rendered inside .checkbox-box::after using the #ico-check SVG symbol from the ds.js sprite when checked.
  4. 4Dash glyph — Rendered via #ico-dash sprite symbol for the indeterminate state.
  5. 5Label.checkbox-label. Always placed to the right of the box. Required for accessibility.

Default

Wrap a native input + a styled box in a <label class="checkbox">. The input is visually hidden but stays in the focus path; the .checkbox-box renders the visible square and shows the right glyph based on :checked / :indeterminate.

HTML
<label class="checkbox">
  <input type="checkbox" class="checkbox-input">
  <span class="checkbox-box">
    <svg class="checkbox-check"><use href="#ico-check"/></svg>
    <svg class="checkbox-dash"><use href="#ico-dash"/></svg>
  </span>
  <span class="checkbox-label">I agree to the terms</span>
</label>

Variants

Three visual states. Unchecked and Checked map to the input's native boolean. Indeterminate is a third visual state used for parent rows in tree / list selection — it can't be set via an HTML attribute, only via input.indeterminate = true in JS.

JavaScript
// Indeterminate is a JS-only property — set it after the input mounts.
document.querySelectorAll('[data-indeterminate]').forEach(el => {
  el.indeterminate = true;
});

States

Three interaction states. Hover and focus are CSS-driven (real interaction required to see them — or use the .is-hover doc helper to force the visual). Disabled uses the HTML disabled attribute on the input.

Full matrix

3 variants × 3 states, mirroring the Figma source. Default row is interactive; Hover row uses the .is-hover documentation helper to force the hover visual at rest; Disabled row uses the native disabled attribute.

Default
Hover
Disabled
Unchecked
Checked
Indeterminate

Accessibility

Built on a native <input type="checkbox"> — all ARIA semantics are implicit. No additional role attributes needed.

Attribute / BehaviourValueNotes
RolecheckboxImplicit from native <input type="checkbox">
Keyboard — toggleSpaceChecks or unchecks a focused checkbox
Keyboard — focusTab / Shift+TabMoves focus between checkboxes and other interactive elements
aria-checked="mixed"Indeterminate stateSet via JS: input.indeterminate = true. CSS alone cannot set this state.
disabledHTML booleanRemoves from tab order and blocks interaction
Label association<label> wrappingAlways wrap input + label in a <label> element. Never use aria-label as a substitute for a visible label.
Group heading<fieldset> + <legend>Use to give a group of related checkboxes a shared accessible name

API

Class / attrOnDescription
.checkbox<label>Wrapper. Makes the whole row a click/tap target for the input inside.
.checkbox-input<input type="checkbox">The native input, visually hidden but accessible and focusable. Drives the visual via :checked / :indeterminate / :disabled.
.checkbox-box<span>The visible 16×16 square with border and fill. Contains the two glyph SVGs.
.checkbox-check<svg>Glyph shown when the input is :checked. Uses sprite symbol #ico-check.
.checkbox-dash<svg>Glyph shown when the input is :indeterminate. Uses sprite symbol #ico-dash.
.checkbox-label<span>The text label. Inherits muted color + reduced opacity when the input is disabled.
checked<input>HTML attribute — checkbox starts checked.
disabled<input>HTML attribute — disables interaction; styling swaps to --checkbox-bg-disabled / --checkbox-checked-bg-disabled.
input.indeterminateJS propertySets the indeterminate visual. Not expressible as an HTML attribute — assign via JS after mount.
.is-hover.checkboxDocumentation helper — forces the hover visual at rest. Used in the Full matrix; not needed in production.