Skip to content
Folio

Forms

Checkbox

A choice that takes effect when the form is submitted.

Usage

When to reach for it

A setting that applies immediately is a Switch.
TSX
import { Checkbox } from '@misoto22/design'

Notes

A choice that takes effect when the form is submitted.

Supports the indeterminate state (checked="indeterminate"), which is what a "select all" header needs when only some rows are selected — a plain unchecked box there tells the reader the opposite of the truth. Controlled or not: the glyph follows the state the box is actually in, so an uncontrolled defaultChecked="indeterminate" draws the dash rather than the tick that says the opposite.

Pair with Field, or wrap it in a <label> at the call site so the words beside it are part of the click target.

Anatomy

Checkbox anatomy
ElementDescription
BoxrequiredAn 18px <button role="checkbox"> on --radius-xs. It fills with --accent for BOTH checked and indeterminate, so the fill says “not off” rather than “on”.
TickThe check glyph, aria-hidden — Radix shows the indicator, the state is carried by the role.
DashThe minus that replaces the tick, chosen from the state the box is actually in — controlled or not, so defaultChecked="indeterminate" draws the dash it promised rather than a tick.
LabelNot rendered here. Unlike RadioGroupItem, nothing wraps the box in a <label>, so the words beside it and the click target they give it are the call site’s job.

Best practices

Do

  • Wrap it and its words in a <label>, or put it in a Field: the control renders no label of its own, so a bare Checkbox has no accessible name and an 18px box is the entire click target.
  • Hold an indeterminate box on controlled state. It is a report about OTHER rows, and clicking it hands you true — a “select all” header that keeps its own answer stops describing the list underneath it on the first click.
  • Default the value on the server. An unticked box sends no entry at all in a form submission, so the field a reader deliberately cleared and a field that was never rendered arrive identically as undefined.

Don’t

  • checked without onCheckedChange gives a box that never moves: Radix treats the prop as the source of truth, so the reader clicks a control that is neither broken nor working and gets no feedback either way.
  • There is no readOnly on a Radix checkbox. disabled is the only lock and it drops the box out of the tab order and out of the form, so a value that must be shown but not changed is better drawn as text.
  • Do not put the indeterminate state on a leaf. It means “some of the things under this one”, so a box with nothing under it that draws a dash is reporting a state its own value cannot hold.

Examples

default

A select-all header over the rows it summarises. Some-but-not-all is the indeterminate state, and it has to be driven through the controlled checked prop: the glyph is chosen from props.checked, so an uncontrolled box never draws the dash and reports the opposite of the truth. The state means “some of the things under this one”, so it only belongs on a header — a leaf that draws a dash is claiming a state its own value cannot hold. Note the labels: the control renders none of its own, so the words, and the click target they give it, are the call site's job.

with descriptions

Box, name, and the line that says what saying yes actually costs — all three inside the label, so the whole row is the click target rather than an 18px square, and the sentence is part of the accessible name. That is the argument for putting the detail inside rather than beside it, and also the reason to keep it to one clause: it is read out every time the row is. The rows align on their first line, because a box centred against two lines of text floats away from the word it belongs to. These commit when the form is saved; a setting that applies on the spot is a Switch.

inside a form

What a submitted form actually carries. An unticked box sends no entry at all — not false, nothing — so a field the reader deliberately cleared and one that was never rendered arrive at the server identically, and the default has to be decided there rather than inferred from the payload. Untick both and save to watch the keys disappear. Each box needs a name to send anything; value is what it sends when it is on.

Keyboard

Checkbox keyboard interactions
KeyDoes
SpaceToggles it.

Accessibility

  • Supports the indeterminate state, which is what a “select all” header needs when only some rows are selected.