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
- Implement checkbox in any Koder UI
Todos os triggers
- Build a multi-select list / form
- Add an opt-in/opt-out toggle (single checkbox)
- Render a parent-children indeterminate group
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-mutedunchecked) - Check icon: 14 px,
accent-onfill - Indeterminate icon: horizontal dash, 12 px wide,
accent-on - Hit zone: 48×48 px (touch target, R5)
- Label (when paired): 8 px gap,
body-mediumrole
R1 — States (visual)
Per interaction/states.kmd:
| State | Box bg | Box border | Icon |
|---|---|---|---|
| Unchecked + enabled | transparent | text-muted (2 px) | — |
| Unchecked + hover | text-muted 8% bg | text-muted | — |
| Unchecked + focused | + focus ring | accent | — |
| Checked + enabled | accent | accent | accent-on check |
| Checked + hover | accent-strong | accent-strong | accent-on |
| Checked + focused | + focus ring | accent | accent-on |
| Indeterminate | accent | accent | accent-on dash |
| Disabled (any) | 38% opacity | 38% opacity | 38% opacity |
| Error | error border | error | error (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
errorcolor - Helper text below with error message (per
errors/user-facing-messages.kmd) aria-invalid="true"+aria-describedbypointing at error text
R8 — Accessibility
<input type="checkbox">element (NOT<div role="checkbox">)- Visible focus ring (R1)
- Keyboard: Space toggles
- Label association MANDATORY
aria-checkedfollows 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
| Preset | Box style |
|---|---|
material3 | Square + radius-xs (2 px), 2 px border |
material2 | Square + sharp corners, 2 px border |
ios_cupertino | Circle (radius-full), 1.5 px border |
gnome | Square + radius-xs, single 1 px border |
windows_95 | 3D bevel border, sharp corners, no radius |
brutalist | Sharp corners, 3 px thick border |
terminal_classic | ASCII [ ] / [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)
Cross-link
interaction/selection.kmd— multi-select patternsinteraction/states.kmd— focus/hover/press visualsthemes/color-roles.kmd— accent + error tokenserrors/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
inputof typecheckbox - 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
accentcolor role - AND a check icon is drawn in the
accent-onrole
Scenario: Indeterminate state renders dash icon
- GIVEN a checkbox in the indeterminate state
- WHEN it is rendered
- THEN the box background uses the
accentrole - AND a horizontal dash icon (not a check) is drawn in the
accent-onrole - 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
errorcolor role - AND the element exposes
aria-invalid="true"andaria-describedbyreferencing the helper error text - AND no required-indicator asterisk is shown on the checkbox row
Referências
specs/interaction/selection.kmdspecs/interaction/states.kmdspecs/themes/color-roles.kmd