Forms
Checkbox
A choice that takes effect when the form is submitted.
Usage
When to reach for it
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
| Element | Description |
|---|---|
| Boxrequired | An 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”. |
| Tick | The check glyph, aria-hidden — Radix shows the indicator, the state is carried by the role. |
| Dash | The 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. |
| Label | Not 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
| Key | Does |
|---|---|
| Space | Toggles it. |
Accessibility
- Supports the indeterminate state, which is what a “select all” header needs when only some rows are selected.