Checkbox
Turns one option on or off, or picks several items from a list.
import { Checkbox } from "@/ui";Usage
Checkbox is controlled: pass checked and flip it in onChange. onChange receives the native change event; most call sites ignore it and toggle their own state.
const [same, setSame] = useState(true);
<Checkbox
label="Same as SEO title tag"
checked={same}
onChange={() => setSame(!same)}
/>The whole row, box and label, is clickable. The label sits 8px to the right of the box.
Select all
Set indeterminate on a parent checkbox when some, but not all, of its children are checked. It shows a dash instead of a tick. Clicking it should check everything; clicking it again when everything is checked should clear everything.
const all = picked.length === DASHBOARDS.length;
const some = picked.length > 0 && !all;
<Checkbox
label="All dashboards"
checked={all}
indeterminate={some}
onChange={() => setPicked(all ? [] : DASHBOARDS)}
/>
{DASHBOARDS.map((d) => (
<Checkbox key={d} label={d} checked={picked.includes(d)} onChange={() => toggle(d)} />
))}Derive checked and indeterminate from the children, as above. Checkbox re-applies indeterminate after every render, so the dash stays even though the browser clears it on click.
States
<Checkbox label="Unchecked" checked={false} />
<Checkbox label="Checked" checked />
<Checkbox label="Indeterminate" indeterminate />
<Checkbox label="Unchecked, disabled" checked={false} disabled />
<Checkbox label="Checked, disabled" checked disabled />
<Checkbox label="Indeterminate, disabled" indeterminate disabled />| Figma state | Box | Hover |
|---|---|---|
| Uncheck | 1px --color-grey-900 border, white | --color-primary-100 fill, same border |
| Check | --color-primary-500 fill, white tick | --color-primary-800 fill |
| Intermediate | --color-primary-500 fill, white dash | --color-primary-800 fill |
| Focus (keyboard) | --shadow-focus ring around the box | |
| Disabled, unchecked | --color-grey-200 border | none |
| Disabled, checked or intermediate | --color-primary-300 fill | none |
The box is 12px square with 1px corners, centred in a 16px space, as in the Figma Checkbox component. When both checked and indeterminate are set, only the dash shows and screen readers hear "mixed". The label is 14px regular --color-text-primary, and --color-text-disabled when disabled.
There is no error state. If a checkbox must be ticked to continue, such as accepting terms, show the reason in text next to it and keep the submit button disabled until it is ticked.
Without a visible label
In a table row or list where the row's own text names the item, leave out label and pass an aria-label that names what the checkbox selects.
<Checkbox
aria-label="Select LinkedIn, Q4 retargeting"
checked={rows.linkedin}
onChange={() => flip("linkedin")}
/>
<span>LinkedIn, Q4 retargeting</span>Props
| Prop | Type | Default | Notes |
|---|---|---|---|
label | ReactNode | "" | Text after the box. When empty, nothing is rendered and you must pass aria-label. |
checked | boolean | false | Controlled. |
indeterminate | boolean | false | Shows a dash. Takes visual priority over checked. |
onChange | (event) => void | Receives the native change event. | |
disabled | boolean | false | |
className | string | Added to the wrapping <label>. Use for layout only. |
Any other prop (name, value, aria-label, aria-describedby, ref) is passed to the <input type="checkbox">.
Accessibility
- It is a native checkbox, visually hidden, inside a
<label>. Space toggles it, and screen readers announce checked, unchecked and mixed (indeterminate) states. - The focus ring shows on keyboard focus only (
:focus-visible). - With no
label, the checkbox has no name unless you passaria-label. Text in a separate<span>beside it, as in the dashboard picker in Workbook chat, is not connected and is not read out. - A group of related checkboxes should sit under a heading or inside a
<fieldset>with a<legend>, so the question is read before the options. Checkbox has no group component.
Do and don't
- Do use a Checkbox when each option is independent and people can pick any number, including none.
- Do write the label as the thing that becomes true when it is ticked: "Same as SEO title tag", "Include archived campaigns".
- Don't use a Checkbox for a setting that takes effect immediately, such as turning tracking on. Use Toggle.
- Don't use Checkboxes for options where only one can be picked. Use Radio.