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
8 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.
- Must
- Set aria-invalid only after a submit or a press on the action, never on the first render. Because a coral box before the person has done anything blames them for nothing.
- Never
- Colour a ticked box or replace its small glaze with a flat fill. Because small glaze (glaze-sm) is how Glaze shows a chosen small control, and a coloured tick reads as a status.
- Must
- Show mixed with indeterminate (the minus), 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.
- Should
- Keep a list of boxes to about eight; past that, search belongs in front of it.
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 line is --line-strong, 3.3:1 on paper (WCAG 1.4.11); ticked is small glaze, a lifted grey to ink, its white tick 6.7:1 or more where it sits (4.8:1 at the very top row).
- The glyph is aria-hidden; mixed reads without colour through the minus.
- The box is 16px with a 28px hit area, and the label makes the whole row the target.
- A group is role="group" named by aria-labelledby.
- Focus is the 2px ink ring, 2px out.
Keyboard
1 key
- Space
- Toggles; a parent cycles all, none, then the set the person picked.
Edge cases
7 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.
- Dark theme
- Ticked turns light glaze with a dark glyph, 10.5:1 or more; unticked is near-black with a 45% line.
- Forced colors
- The glaze drops to Highlight with a HighlightText glyph; unticked draws a CanvasText border.
- Reduced motion
- No press scale; the tick appears at once.
API
7 props
checked, defaultChecked, onCheckedChangeBase UI Checkboxindeterminatebooleandefaultfalseparentbooleandefaultfalsevaluestringaria-invalidbooleandefaultfalsedisabled, readOnly, required, nameBase UICheckboxGroup value, onValueChange, allValuesstring[]
Tokens
17 tokens
--paper- #ffffff
--field- #efefef
--pressed- rgb(5 5 5 / 0.10)
--line-strong- rgb(5 5 5 / 0.45)
--line-faint- rgb(5 5 5 / 0.06)
--text-2- rgb(5 5 5 / 0.60)
--fail- #e35e49
--glaze-sm-top- #6b6b6b
--glaze-sm-mid- #3d3d3d
--glaze-sm-bottom- #232323
--glaze-sm-highlight- rgb(255 255 255 / 0.30)
--glaze-sm-rim- rgb(0 0 0 / 0.55)
--glaze-sm-text- rgb(255 255 255 / 0.92)
--radius-tag- 6px
--focus- rgb(5 5 5 / 0.80)
--dur-state- 160ms
--dur-press- 120ms