Checkbox
Zero or more, chosen independently, and staged until something else commits them.
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
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.
- Indeterminate is the element's own property, not an
aria-checked="mixed"attribute — on a native checkbox that attribute argues with the state the element already reports. This page claimed the attribute for a long time while nothing set either, so a select-all over a partly-chosen list announced itself as plainly unchecked. - A select-all names the set it covers — “Select all 4 shown” or “Select all 19”, never a bare “Select all” beside a filtered list. See List, where the ambiguity is worked through.
- The whole label row is the target, not the 20 px box. In gloves the difference is between a control that can be operated and one that cannot.
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.
| Rule | Held by | How |
|---|---|---|
| Indeterminate reaches the element | The 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 target | The 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 displayed | The component | aria-describedby, wired from the generated id.Asserted: Selection controls: a hint is described, not just displayed |
| A group states its shared question | The 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 so | The 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 reachable | The 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 one | The 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 moves | You | 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 covers | You | The label is a string. “Select all” beside a filtered list is a coin toss, and the type permits it. |
| The label is positive | You | “Do not share” is as expressible as “Share”, and asks the reader to hold an inversion while they work. |
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
Indeterminate states its count, so a partial selection is not read as complete.
A dash with no count. Two of four and three of four look identical.
☐ 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.
☑ 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.
☐ Patient has no known allergies
Positive wording: the tick means what it says.
☐ 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
- A real
<input type="checkbox">with a bound<label>. A styled div loses the state, the role and every keyboard behaviour the platform provides for free. - The hint is referenced by
aria-describedby, not merely rendered beside the box — it existed for people who could see it and for nobody else until that was wired. - Groups are a
fieldsetwith alegend, so the shared question is announced with the options rather than sitting near them. - Labels are positive. A box that must be ticked for something not to happen asks the reader to hold an inversion while they work.
Outcomes of use
- Effectiveness — the clinician can tell a full selection from a partial one without opening the list.
- Efficiency — one press per option, and the whole row is the target.
- Satisfaction — nothing takes effect until it is submitted, so a mis-tap is recoverable. Attributes believed to contribute to an outcome; the outcome is settled by observing real use, not by this page — see Usability & context of use.
Clinical safety notes
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.
- Nothing is committed when a box moves. (you) Mitigates: a setting changed by somebody who believed they were staging it.
- An indeterminate box states its count. (the component references the hint; you supply the count) Mitigates: a partial selection read as complete before a bulk action.
- The label row is the target. (the component) Mitigates: a mis-tap on an adjacent option in gloves — see Density & scaling.
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.
Related
- Selection controls — which of the three to use.
- Radio — exactly one, from a set shown in full.
- Switch — the same shape, applied at once instead of staged.
- List — what a select-all is allowed to claim.