Components · Forms

Checkbox

Zero or more, chosen independently, and staged until something else commits them.

Stable · v1.0 shadcn/ui WCAG 2.2 AA

Overview

A checkbox stages a change. Nothing happens when it moves; something happens when the form is submitted. That is the whole difference from a Switch, and using the wrong one is how a setting gets changed by somebody who thought they were preparing a change.

Which of the three to reach for — how many may be chosen, and when the choice takes effect — is decided on Selection controls.

Some of the set, and how much of it

The rule

An indeterminate checkbox states its count. Two of four and three of four render identically otherwise, and the control that is supposed to summarise a selection instead conceals it.

What the component enforces, and what it only asks for

The control holds its own semantics and its target size. What it cannot hold is what a selection means.

RuleHeld by How
Indeterminate reaches the elementThe component Applied as the DOM property, which is the mechanism that makes a screen reader say “mixed”. The page claimed an aria-checked attribute for a long time while nothing set either.
Asserted: Checkbox: indeterminate reaches the element, not just the styling
The whole label row is the targetThe component 44 px, from the shared base file, so the rule cannot drift between checkbox, radio and switch.
Asserted: Checkbox: the label row is the target, not the 20px box
The hint is referenced, not merely displayedThe component aria-describedby, wired from the generated id.
Asserted: Selection controls: a hint is described, not just displayed
A group states its shared questionThe component CheckboxGroup is a real fieldset with a legend.
Asserted: CheckboxGroup: the shared question is a real legend
A mixed box stays mixed while its prop says soThe component Re-applied on every render. A click clears the DOM property and the effect only ran when the prop changed, so a parent still passing indeterminate showed a checked box.
Asserted: Checkbox: indeterminate survives a re-render after a click
An unavailable box says why, and stays reachableThe component disabled is not a prop, as on Button. unavailable={{ reason }} keeps the control focusable, describes it by the reason, and ignores Space and clicks. It accepted a bare native disabled: off, unreachable by keyboard, and silent about why.
Asserted: Checkbox, radio and switch cannot be switched off without a reason
A mixed box is drawn differently from an empty oneThe component A bar across the middle. The box is drawn by the stylesheet and had no indeterminate styles, so “some selected” was the same empty square as “none selected”. Measured with the stylesheet a consumer has.
Asserted: Layout: a mixed checkbox looks different from an empty one
Nothing is committed when a box movesYou The component is uncontrolled unless you control it. Whether ticking one changes anything before submit is entirely your wiring — and a checkbox that applies on change is a Switch in the wrong clothes.
A select-all names the set it coversYou The label is a string. “Select all” beside a filtered list is a coin toss, and the type permits it.
The label is positiveYou “Do not share” is as expressible as “Share”, and asks the reader to hold an inversion while they work.
Why this is written down

A rule stated on a component page reads as a rule the component keeps. 4 of these 7 are; 3 are obligations this page was quietly handing to the reader. The mechanics are held. Whether ticking a box does something now or later is the one distinction this whole page is built on, and only your wiring decides it.

Do's and don'ts

Do
– Laboratory results2 of 4 selected

Indeterminate states its count, so a partial selection is not read as complete.

Don't
– Laboratory results

A dash with no count. Two of four and three of four look identical.

Do

☐ Include ECG traces

☐ Include laboratory results

Export · nothing changes until you press it

Ticking stages the change. The clinician can tick, untick and reconsider, and the export happens once, when they say so.

Don't

☑ Share this record with the referring GP

shared 14:32:07

It applied on the tick. A checkbox that acts on contact is a switch that looks like a form, and there was no moment at which anyone reviewed the sharing.

Do

☐ Patient has no known allergies

Positive wording: the tick means what it says.

Don't

☐ Do not withhold allergy information

A double negative. The reader has to work out that ticking this box means withhold, and under interruption they will get it backwards.

Accessibility

Outcomes of use

Clinical safety notes

Risk controls this page specifies

Trace these in your risk file (ISO 14971) and usability engineering file (IEC 62366-1). Which of these the component keeps and which are yours is stated in the guarantees table; a control the component does not keep is one your risk file must show you keep.

Implementation

$ npx shadcn@latest add https://md.notjustany.tech/r/checkbox.json
<CheckboxGroup legend="Include in export">
  <Checkbox label="ECG traces" defaultChecked />
  <Checkbox label="Laboratory results" indeterminate
    hint="2 of 4 selected" />
</CheckboxGroup>

// Ships with ui/choice-base.tsx, shared with Radio and Switch, so the
// 44px row and the hint wiring cannot drift between the three.
NotJustAnyMed.Tech Design System · Selection controls · v1.0 · draft for review
Covers Checkbox, Radio and Switch. Reference applications named in this system are fictional.