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, onCheckedChange
Base UI Checkbox
indeterminate
boolean default false
parent
boolean default false
value
string
aria-invalid
boolean default false
disabled, readOnly, required, name
Base UI
CheckboxGroup value, onValueChange, allValues
string[]

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

Install

For your coding agent

GitHub

Please star me pookie

141

A new design system every week

Leave your email before you copy the prompt.