Pular para o conteúdo

Checkbox

components specs/components/checkbox.kmd

Binary toggle in a multi-select context — user marks 0..N options. Material parity (`/components/checkbox/overview`). 18×18 px tap target wrapped in 48 px hit zone; 3 states (unchecked, checked, indeterminate); WAI-ARIA-compliant.

Quando esta spec se aplica

Triggers primários

Todos os triggers

Corpo da especificação

Spec — Checkbox

Facet Visual do Koder Design. Material parity: https://m3.material.io/components/checkbox/overview.

Anatomy

☐  Unchecked
☑  Checked     (filled accent square + white check icon)
☒  Indeterminate (filled accent square + dash icon)
  • Box: 18×18 px (default), 2 px border (text-muted unchecked)
  • Check icon: 14 px, accent-on fill
  • Indeterminate icon: horizontal dash, 12 px wide, accent-on
  • Hit zone: 48×48 px (touch target, R5)
  • Label (when paired): 8 px gap, body-medium role

R1 — States (visual)

Per interaction/states.kmd:

StateBox bgBox borderIcon
Unchecked + enabledtransparenttext-muted (2 px)
Unchecked + hovertext-muted 8% bgtext-muted
Unchecked + focused+ focus ringaccent
Checked + enabledaccentaccentaccent-on check
Checked + hoveraccent-strongaccent-strongaccent-on
Checked + focused+ focus ringaccentaccent-on
Indeterminateaccentaccentaccent-on dash
Disabled (any)38% opacity38% opacity38% opacity
Errorerror bordererrorerror (if checked)

R2 — Single checkbox (boolean toggle)

Use when the user toggles ONE thing on/off (terms acceptance, notification opt-in).

☐  I agree to the Terms of Service.
  • Label to the right (left in RTL)
  • Whole row clickable (label is a <label for="id"> or wraps the input)
  • Don't use a Switch instead — Switch implies immediate effect (toggle WiFi), checkbox implies "this value as part of a form"

R3 — Multi-select list

Use within a list when user picks 0..N items.

☑  Inbox
☐  Spam
☑  Drafts
☐  Sent
  • Per interaction/selection.kmd — list-row pattern
  • Whole row clickable (not just the checkbox)
  • Long-press on mobile enters select mode; subsequent taps toggle
  • Bulk action bar appears when ≥ 1 checked

R4 — Indeterminate state

Represents partial selection (e.g., a parent of a tree with some children checked).

☒  All projects        ← indeterminate (some children checked)
   ☐  Project A
   ☑  Project B
   ☑  Project C
  • Visual: dash icon (not check)
  • Behavior on click: depends on convention — Koder default: indeterminate → unchecked → checked → unchecked (3-cycle)
  • Programmatic only — user can't directly SET indeterminate; it's derived from children state
  • aria-checked="mixed"

R5 — Touch target

  • Visible box: 18×18 px
  • Hit zone: 48×48 px (touch target per safe-area.kmd)
  • When in a list row: entire row is the hit zone; checkbox box is visual anchor

R6 — Label association

<!-- ✅ Wrapped (preferred) -->
<label>
  <input type="checkbox"> I agree
</label>

<!-- ✅ Linked via for -->
<input type="checkbox" id="agree">
<label for="agree">I agree</label>

<!-- ❌ Separate without association -->
<input type="checkbox">
<span>I agree</span>

R7 — Error state

When a required checkbox is unchecked at submit:

  • Box border error color
  • Helper text below with error message (per errors/user-facing-messages.kmd)
  • aria-invalid="true" + aria-describedby pointing at error text

R8 — Accessibility

  • <input type="checkbox"> element (NOT <div role="checkbox">)
  • Visible focus ring (R1)
  • Keyboard: Space toggles
  • Label association MANDATORY
  • aria-checked follows native input checked state (no need to set manually unless using ARIA-only widget)
  • Screen reader announces: "Checkbox, Inbox, checked" (or unchecked, or indeterminate)

R9 — Per-preset variation

PresetBox style
material3Square + radius-xs (2 px), 2 px border
material2Square + sharp corners, 2 px border
ios_cupertinoCircle (radius-full), 1.5 px border
gnomeSquare + radius-xs, single 1 px border
windows_953D bevel border, sharp corners, no radius
brutalistSharp corners, 3 px thick border
terminal_classicASCII [ ] / [x]

R10 — Forbidden patterns

  • ❌ Custom checkbox without standard semantic (rebuilding from <div>)
  • ❌ Checkbox without label (always provide accessible name)
  • ❌ Toggling between checkbox and switch styling per-row in same list
  • ❌ "Required" indicator on checkbox row (it's binary — error on submit, not visual asterisk)
  • interaction/selection.kmd — multi-select patterns
  • interaction/states.kmd — focus/hover/press visuals
  • themes/color-roles.kmd — accent + error tokens
  • errors/user-facing-messages.kmd — error format

Requirements (testable)

Requirement: Semantic checkbox element {#req-checkbox-semantic-element}

The checkbox SHALL be rendered as a native <input type="checkbox"> element and SHALL NOT be rebuilt from a generic <div role="checkbox">, so that native semantics, focus, and keyboard behavior are preserved.

Scenario: Native input in the accessibility tree

  • GIVEN a checkbox rendered in any Koder UI
  • WHEN the accessibility tree is inspected
  • THEN the exposed node is a native input of type checkbox
  • AND no <div role="checkbox"> substitute is used for the control

Scenario: Space key toggles the checkbox

  • GIVEN a focused, enabled checkbox in the unchecked state
  • WHEN the user presses the Space key
  • THEN the checkbox transitions to the checked state
  • AND pressing Space again returns it to the unchecked state

Requirement: Accessible name via label association {#req-checkbox-label-association}

Every checkbox SHALL have an associated accessible name, provided either by wrapping the input in a <label> or by linking a <label for="id"> to the input's id; a checkbox presented without label association SHALL NOT ship.

Scenario: Label wraps the input

  • GIVEN a checkbox whose <input> is nested inside a <label> element
  • WHEN a screen reader queries the control's accessible name
  • THEN the name resolves to the label's text
  • AND clicking anywhere on the label toggles the input

Scenario: Label linked via for attribute

  • GIVEN a checkbox <input id="agree"> and a sibling <label for="agree">
  • WHEN the accessible name is computed
  • THEN it resolves to the linked label's text
  • AND the checkbox is not shipped with an unassociated <span> in place of a label

Requirement: Three visual states {#req-checkbox-states}

The checkbox SHALL render exactly one of three states — unchecked, checked, or indeterminate — with the state-specific box fill, border, and icon defined in R1, and the checked and indeterminate states SHALL use the accent box fill with an accent-on icon.

Scenario: Checked state renders accent box with check icon

  • GIVEN an enabled checkbox
  • WHEN it enters the checked state
  • THEN the box background and border use the accent color role
  • AND a check icon is drawn in the accent-on role

Scenario: Indeterminate state renders dash icon

  • GIVEN a checkbox in the indeterminate state
  • WHEN it is rendered
  • THEN the box background uses the accent role
  • AND a horizontal dash icon (not a check) is drawn in the accent-on role
  • AND the element exposes aria-checked="mixed"

Requirement: Touch target hit zone {#req-checkbox-touch-target}

The visible checkbox box SHALL be 18×18 px and its interactive hit zone SHALL be at least 48×48 px; within a list row the entire row SHALL act as the hit zone with the box as the visual anchor.

Scenario: Standalone checkbox hit zone

  • GIVEN a standalone checkbox with an 18×18 px visible box
  • WHEN the pointer or touch hit zone is measured
  • THEN it spans at least 48×48 px centered on the box

Scenario: List row is fully clickable

  • GIVEN a checkbox rendered inside a multi-select list row
  • WHEN the user clicks or taps anywhere on the row, not only the box
  • THEN the row's checkbox toggles its checked state

Requirement: Indeterminate is programmatic-only {#req-checkbox-indeterminate-derived}

The indeterminate state SHALL be derived programmatically from children selection and SHALL NOT be a value the user can set directly by activating the control; a direct click on an indeterminate parent SHALL follow the Koder 3-cycle indeterminate → unchecked → checked → unchecked.

Scenario: Clicking an indeterminate parent follows the 3-cycle

  • GIVEN a parent checkbox in the indeterminate state because some children are checked
  • WHEN the user clicks the parent
  • THEN the parent moves to the unchecked state
  • AND a subsequent click moves it to checked
  • AND a further click returns it to unchecked, never back to indeterminate by direct activation

Requirement: Required-checkbox error state {#req-checkbox-error-state}

When a required checkbox is unchecked at submit, the control SHALL render the error border color, expose aria-invalid="true" with aria-describedby pointing at the error text, and SHALL NOT display a visual "required" asterisk on the row.

Scenario: Submit with a required checkbox unchecked

  • GIVEN a required checkbox left unchecked
  • WHEN the form is submitted
  • THEN the checkbox box border uses the error color role
  • AND the element exposes aria-invalid="true" and aria-describedby referencing the helper error text
  • AND no required-indicator asterisk is shown on the checkbox row

Referências