Checkbox
Checkbox — one choice among any number, or a single agreement.
Beta · Forms · packages/css/src/components/checkbox.css
.pui-checkbox · __input (visually hidden) · __box · __label · :checked :indeterminate · --error · :disabled.
Sizes across, states down.
What it is for
Section titled “What it is for”- Checked: false, true, indeterminate — a minus, for a parent whose children are mixed
- Five states: default, hover, focus, error, disabled. Hover darkens the border, or the fill to action/primary/bg-hover when checked; focus is border/focus with the 3px ring at 15%; error is border/error — the whole box when checked
- Label is a text property and can be hidden (Has label) when the row itself is the label — a table header, a card
- The border is border/default and the label text/default
Use when the user can pick any number of options, or must confirm one thing — the privacy line on every site lead form.
Do not use when exactly one option must be chosen (Radio); the choice takes effect at once without a submit (Switch); the list is long (a multi-select Combobox).
Relates to Radio, Switch, Field (a checkbox row sits in a Field with its error), Label.
Anti-patterns a checkbox that submits on change · a label written as a question — labels state the thing · a checkbox and a switch in one form · an unlabelled box that leans on the text beside it.
Keywords: checkbox, check, tick, agree, consent, multi-select, indeterminate
Figma — page Checkbox, Radio & Switch: Checkbox (15 variants)
shadcn: checkbox
The markup — copy this
Section titled “The markup — copy this”The class, the parts, and nothing that is not in the package. The same block opens checkbox.css.
<label class="pui-checkbox"> <input class="pui-checkbox__input pui-sr-only" type="checkbox"> <span class="pui-checkbox__box"></span> <span class="pui-checkbox__label">I agree to the privacy policy</span></label>The modifiers
Section titled “The modifiers”| What it is | Note | |
|---|---|---|
| State | :hover · :focus-visible · :checked · :indeterminate · error = --error or [aria-invalid=“true”] on the input · :disabled (the row dims to .5) | — |
Accessibility — measured, not assumed
Section titled “Accessibility — measured, not assumed”Every number here is measured on the rendered component: contrast (npm run contrast:render), the focus ring (npm run focus) and the keyboard (npm run keyboard). The floors are principle 12’s.
Contrast
Section titled “Contrast”| ink on ground | reads | floor | the matrix calls it | |
|---|---|---|---|---|
#0b0202 on #ffffff |
20.51 | 4.5 | text.default on surface.page (promised) | ok |
1 distinct pair(s) rendered, 0 that axe calls a violation.
The focus ring
Section titled “The focus ring”| primitive | on | the indicator | over | the change | |
|---|---|---|---|---|---|
| Checkbox | surface/page | shadow #6a66e4 2 px |
#ffffff |
4.53 | ok |
| Checkbox | surface/card | shadow #6a66e4 2 px |
#f2f2f7 |
4.06 | ok |
| Checkbox | surface/sunken | shadow #6a66e4 2 px |
#f2f2f2 |
4.05 | ok |
| Radio | surface/page | shadow #6a66e4 2 px |
#ffffff |
4.53 | ok |
| Radio | surface/card | shadow #6a66e4 2 px |
#f2f2f7 |
4.06 | ok |
| Radio | surface/sunken | shadow #6a66e4 2 px |
#f2f2f2 |
4.05 | ok |
5 of 5 focus stop(s) in this block meet WCAG 2.4.13 on screen — the same crop screenshotted focused and unfocused, and the difference measured.
Keyboard
Section titled “Keyboard”What the consumer’s script owes. nothing to write for Radio: native inputs sharing a name ARE a radio group and the browser gives the roving tabindex and the arrow keys. Checkbox takes Space, also the browser’s.
Tab order across the whole library is document order over 344 stops, and Shift+Tab is its exact reverse — no keyboard trap anywhere (WCAG 2.1.2).
Pepper UI v1.5.0
The Mobupps design system. 386 tokens, 88 components, 65 pages in the workshop — every page of this site generated from the repository, so nothing here is typed twice.