Design system

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 stateBoxHover
Uncheck1px --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 bordernone
Disabled, checked or intermediate--color-primary-300 fillnone

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

PropTypeDefaultNotes
labelReactNode""Text after the box. When empty, nothing is rendered and you must pass aria-label.
checkedbooleanfalseControlled.
indeterminatebooleanfalseShows a dash. Takes visual priority over checked.
onChange(event) => voidReceives the native change event.
disabledbooleanfalse
classNamestringAdded 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 pass aria-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.

On this page