Checkbox
A box the reader ticks to choose an option that stands on its own, where any number can be chosen, including none. When only one of several can be chosen, use a radio button group, and when the change applies the moment it is made, use a toggle.
Live preview
Rendered by the component itself from the same generated tokens as the rest of the site.
<Checkbox
label="Save as default address"
checked
onChange={setChecked}
/>Installation
Install it with the shadcn CLI, along with everything it uses. New project? Set it up first, as Quick start shows.
npx shadcn@latest add https://www.graphite-ui.com/r/checkbox.jsonimport { Checkbox } from '@/components/ui/checkbox'Anatomy
Slots come from the contract, so this list and the code cannot disagree about what the component is made of.
- LabelRequiredAlways paired, never a bare checkbox. The control renders it itself. There is no wrapper to take it from — the kit builds label text into each form control.
- Supporting textOptionalHelp, warning or error text, 8 below the row. The kit calls this Helper / Warning / Error text and builds it into the control the same way; warning and error lead with the shared 16px status icon.
Variants
Selection is two props, checked and indeterminate. Indeterminate is a dash in the same box, a different glyph rather than a different color, so it reads apart from both of the others. Indented steps a child in under its parent.
States
Focus is a pseudo-class in the code and a variant in the kit, so the page forces it here. The kit draws no hover state for a checkbox and the code has none. Error is not a prop of its own: passing errorText is what puts the control in it; the box’s ring turns danger, over the fill when checked, and the message leads with the status icon. Warning leaves the box alone. Read-only draws the empty box in the disabled tone with the mark still on it.
API reference
Generated from the contract, versioned with it, and checked by drift-check. This table cannot describe props the component does not have.
| Prop | Type | Default | Notes |
|---|---|---|---|
| checked | — | — | |
| indeterminate | — | — | |
| disabled | — | — | The glyph, the label and the help text all take the disabled tone; a checked box stays filled. |
| readOnly | boolean | — | The kit's Read-only. Native checkboxes ignore readonly, so the box stays focusable, carries aria-readonly and never reports a change. It draws the empty box in the disabled tone with the mark on it. |
| indented | boolean | — | The kit's Indented, a 28 step in (one 20px frame and its 8 gap), for a child under an indeterminate parent. |
| label | — | — | Required. There is no shape in which this control exists unlabelled, and no wrapper left to supply one. |
| helpText | — | — | Supporting copy. Suppressed while errorText or warningText is present. |
| warningText | — | — | Resolves the kit's Warning, outranked by an error. The box is unchanged; the warning is carried by the message's status icon. |
| invalid | boolean | — | The error state without a message of its own, for a box whose group carries the message (Checkbox group, #273). The ring and aria-invalid as for errorText. |
| errorText | — | — | Its presence resolves the error state, so error text and error styling cannot be shown apart. This was Field's guarantee and it survives Field. |
Design tokens
Every swatch is live. Change the source color in the header and this table repaints, because it reads the same roles the component does.
on-surfaceThe box, its ring when unchecked and its fill when checked or indeterminate, which the kit binds to icon-primary; the label text; warning text. The check and dash are knocked out of the fill, so they show what the box sits on.primaryThe focus ring, through the family's focus step, a 2px stroke centred on the 20px frame; and the disabled and read-only tone, through its disabled-content step.on-surface-variantHelper text, which the kit binds to onSurfaceVariant rather than onSurface.dangerThe error ring, at the ring's own weight and over the fill when checked; the error text; the error status icon's triangle. One role for all three, so they cannot drift apart.warningThe warning status icon's triangle.on-warningThe "!" on the warning status icon.backgroundThe "!" on the error status icon.textThe label at Body/3.spacingThe row and stack gaps, and the 32px touch target around the 20px frame.
Usage
The contract's prohibitions, written as the choices you will actually face.
Do
- Use a checkbox when each option stands alone and any number of them can be on, including none.
- Show indeterminate on a parent whose children are only partly selected, and let the next click resolve it.
- Show an error by passing errorText, so the message and the error state arrive together.
- Write the label as the thing being chosen (“Save as default address”), not as a question.
Don’t
- Render a bare box and let nearby text stand in for its label. The label prop is required for exactly this reason.
- Shrink the hit area to the painted box. The box is 15px in a 20px frame but the control keeps a 32px target, the same rule as Button.
- Mark indeterminate by recoloring the check. It is a dash, so it still reads when color does not.
- Use a checkbox for a setting that takes effect the moment it changes. That is a Toggle.
Accessibility
What the component does for you, and what it leaves to you.
- Keyboard
- It is a native checkbox, so Tab reaches it and Space toggles it. Clicking the label toggles it too.
- Roles
- A native input of type checkbox. Indeterminate is written to the DOM property, so assistive tech announces it as mixed rather than unchecked. In error the input carries
aria-invalid; read-only carriesaria-readonlyand stays focusable, but does not change. - Labels
- The label is a real
labeltied to the input. Help and error text are linked witharia-describedby, and error text is announced as it appears. - Focus
- A 2px ring in
--graphite-primary-focuson the 20px frame, 1.5px clear of the box, on keyboard focus only. In error it sits outside the danger ring rather than replacing it. - Target
- The painted box is 15px in a 20px frame. The input underneath covers a 32px square, so the target does not shrink with the drawing.
Figma parity
The kit’s Checkbox page has two sets: the single Checkbox, with Selection and State axes, and a Checkbox group. The code has the single control on every axis and no group. This table is where those facts are reconciled instead of quietly diverging.
| Kit axis | Values | Code | How it maps |
|---|---|---|---|
| Selection | Unchecked · Checked · Indeterminate | checked, indeterminate | Two booleans. When both are set, the dash wins. |
| Glyph | icon-primary ring and filled box | — | The kit’s own glyphs, in on-surface, with the check and dash knocked out. They were a primary fill on a 20px bordered box until 3.0.0. |
| State | Enabled · Focus · Disabled | disabled | Focus is :focus-visible, a pseudo-class (governance rule 7). Disabled takes the glyph, label and help text to the disabled tone. |
| State | Invalid | errorText | Its presence is the error state. The ring turns danger at its own weight; a checked box keeps its fill under it. |
| State | Warning | warningText | The box is unchanged; the message leads with the warning status icon and stays on-surface, as the kit draws it. |
| State | Read-only | readOnly | The box in the disabled tone with the mark on it, no fill. Focusable, never changes. |
| State | Skeleton | — | No counterpart by rule. |
| Indented | False · True | indented | A 28 step in: one 20px frame and its 8 gap. |
| Label | Group label above the box | — | The kit’s Label belongs to the Checkbox group; on a single checkbox the label prop is the value. |
| Set | Checkbox group | — | No counterpart: no current demand under rule 6. Lay checkboxes out in your own container; the set is carried on the plan for unclaimed sets. |
