Checkbox
Turns one option on or off.
Checked
falsedefault
true
indeterminate
States
rest
hover
held
focus-visible
ticked
mixed
invalid
disabled
Usage
When to use it
- Use
- A form asks a yes or no that applies when the form is saved.
- Use
- The person picks any number from a short visible list, none included.
- Use
- Rows are selected for a bulk action, with select-all in the head.
- Not for
- The setting applies the moment it changes. Use Switch.
- Not for
- Exactly one option can be chosen. Use Radio group.
- Not for
- Two to five views of the same thing. Use Segmented control.
Rules
7 rules
- Must
- Wrap the checkbox and its text in one <label> (or Label), or use a horizontal Field. Because a loose span is neither the accessible name nor the target, so a screen reader hears "checkbox" and only the 16px box toggles.
- Must
- Build select-all as <Checkbox parent/> inside a CheckboxGroup with allValues; never work out mixed by hand. Because a hand toggle from mixed clears or fills everything, where parent cycles back to the set the person picked.
- Never
- Fill a ticked box by hand, in yellow or a status colour; the checked recipe already fills it. Because a hand-set fill drops the ink tick, the gold edge and the light, and green or red reads as a job's status; the checked recipe is yellow under an ink tick in light and the yellow tint in dark.
- Must
- Set aria-invalid only after a submit or a press on the action, never on the first render. Because a red box before the person has done anything blames them for nothing.
- Must
- Show mixed with indeterminate (the bar), never a lighter fill. Because a lighter fill reads as disabled, and forced colors drop it so mixed looks unticked.
- Should
- Say what ticking does, in the positive. Because a negative label makes the person undo a double negative to know what happens.
- Should
- Use items-start on a row whose label can wrap or carries a description.
State order
8 states, highest first
- 1
- disabled
- 2
- invalid
- 3
- mixed
- 4
- ticked
- 5
- held
- 6
- focus-visible
- 7
- hover
- 8
- rest
Accessibility
6 checks
- role="checkbox" with aria-checked true, false or mixed, named by the wrapping label.
- The unticked edge is --line-control, 3.5:1 on its body and 3.7:1 on it lifted to white (WCAG 1.4.11).
- Ticked is sodium yellow with a gold edge and an ink tick, 12.8:1, in light; the dark yellow tint with a yellow edge and tick, 9.0:1, in dark. The box is lit from below.
- The mark is aria-hidden; mixed reads without colour through the bar.
- The box is 16px with a 28px hit area, and the label makes the whole row the target.
- Focus is the 2px --focus ring, 2px out.
Keyboard
1 key
- Space
- Toggles; a parent cycles all, none, then the set the person picked.
Edge cases
6 edge cases
- Long wrapping label
- Use items-start so the box sits on the first line.
- No label
- Announced as "checkbox" with no name; pass aria-label.
- Inside a clickable row
- The box stops its click from bubbling, so the row does not open.
- Select-all over hundreds of values
- The parent reads the value array, not the rendered boxes.
- Forced colors
- Ticked takes Highlight with a HighlightText tick and no light; unticked draws a CanvasText edge.
- Reduced motion
- The fill and its light cut in with no fade.
API
7 props
checked, defaultChecked, onCheckedChangeBase UI Checkboxindeterminatebooleandefaultfalseparentbooleandefaultfalsevaluestringaria-invalidbooleandefaultfalsedisabled, readOnly, required, nameBase UICheckboxGroup value, onValueChange, allValuesstring[]
Tokens
15 tokens
--check-off- var(--secondary-bg)
--check-off-hover- #ffffff
--well- #f4f3ee
--line-control- #8a8478
--line- #e2dfd5
--fail- #b3261e
--check-bg- #ffd400
--check-edge- #d9b400
--check-mark- #161512
--primary-light- rgb(255 255 255 / 0.55)
--primary-light-hover- rgb(255 255 255 / 0.85)
--focus- #7a5d00
--dur-light- 240ms
--dur-glow- 320ms
--ease-soft- cubic-bezier(0.22, 0.8, 0.24, 1)