Checkbox
A 20px check control on the primary, with a 44px coarse hit area.
Checkbox is a yes or no answer that a form submits, like opting into an email or accepting
the terms, and several in a list each stand on their own. The 20px box fills with
bg-tone, the primary unless you set a tone, and draws a bold check when it is checked. It has no label of its own, so pair
it with Field and do not ship a box without one.
Accept the terms to continue.
Usage
import { Checkbox } from '@/components/ui/checkbox';
import { Field, FieldLabel, FieldRow } from '@/components/ui/field';
<Field>
<FieldRow>
<Checkbox
id="weekly-summary"
checked={weekly}
onCheckedChange={(checked) => setWeekly(checked === true)}
/>
<FieldLabel htmlFor="weekly-summary">Email me a weekly summary</FieldLabel>
</FieldRow>
</Field>The id on the box and the htmlFor on the label tie them together, so a click on the label
checks the box and the label becomes its name.
Holding the checked state
Pass checked and onCheckedChange to hold the state yourself, as in the usage above, or
defaultChecked to let the box keep it. onCheckedChange hands you true, false, or
"indeterminate", so compare with === true when you store a boolean. The box sets
data-state to checked, unchecked, or indeterminate for your own styles.
Radix also takes checked="indeterminate", for a parent box whose children are only partly
checked, and reports it to assistive tech as mixed. A mixed box fills like a checked one and
draws a dash, and a click on it checks it. Below, the parent box follows its three children.
A description under the label
FieldRow align="start" with a FieldContent stacks a description under the label, and the
box stays on the first line, because the label's line is 20px tall like the box. Field does
not connect the description to the box, so give the description an id and point the box's
aria-describedby at it, and a screen reader reads it after the name.
Email me when someone mentions me in an issue or a comment.
A digest of what changed in your projects, every Monday.
In a form
Inside a <form>, Checkbox renders a hidden native checkbox beside the box and keeps it in
step, so the form sends value under name while the box is checked and nothing while it is
not. value is "on" unless you set one, and boxes that share a name send a list.
required goes on the hidden input too, so the browser holds the submit until the box is
checked, and a form reset puts the box back where it started. Outside a form there is no
hidden input, unless you pass form with the id of a form elsewhere on the page.
To show that a box needs attention, set aria-invalid and its border turns
border-destructive. Put the reason in a FieldError, which has role="alert" so it is read
out as it appears, and point aria-describedby at it, as the terms box at the top of the page
does. Save and Reset below show what the form sends.
Tone
A checked box fills with the primary by default. Set tone to danger, success, warning, info, or
neutral to change the hue, or inherit to take the tone of the surrounding scope. An invalid box still
turns its border to the danger color, whatever its tone.
Accessibility
- It renders a
<button type="button" role="checkbox">witharia-checked, so assistive tech announces a checkbox and whether it is checked, or mixed when it is indeterminate. - Space toggles it. Radix stops Enter, so Enter does not.
- The name comes from the
FieldLabelwhosehtmlFormatches the box'sid. A box with no visible label needs anaria-label. requiredsetsaria-required, andaria-invalidreports the box as invalid as well as painting its border. Field does not wire a description or an error to the box, so setaria-describedbyyourself.- Focus shows a 2px ring on
:focus-visibleonly, offset from the box, in the contrast ink rather than the brand color. - In forced colors the border is
ButtonText, a checked box fills withHighlightunder aHighlightTextcheck, and a disabled one turnsGrayText. A box that is disabled and checked or mixed fills withGrayTextunder aCanvascheck or dash. Focus keeps a transparent outline that the system paints, so it stays visible in Windows high contrast. - The box is 20px. On a coarse pointer, such as a touch screen, an invisible 44px square centered on it takes the tap too, and the label is a target as well. With a mouse the extra area is off.
disabledsets the nativedisabledattribute, so the box leaves the tab order, is reported as unavailable, and theFieldLabelbeside it dims.- The check scales in over 120ms. Under reduced motion it appears at once, and the fill changes without a transition.
When to use something else
- Switch for a setting that takes effect the moment it flips, as in a settings list, with no submit.
- Radio group when the options exclude each other, so exactly one of a few is chosen, and Select when that one comes from a long list.
- Toggle for a toolbar button that stays pressed, like bold, which has no label beside it and no form value.
Install
shadcn add @gesso/checkbox