Checkbox
A tri-state checkbox that owns its label and its error message. One size, no variants — error, disabled, and indeterminate are states.
className styles the wrapper
Checkbox renders a wrapper around the box so it can hold the label and the error message. className styles that wrapper, not the box. Every other prop passes through to the Radix root, the same divergence from stock shadcn that Input carries.
States
One 20px box and a 20px row, so a checkbox that starts a row leaves no dead space above or below it. Checked and indeterminate both take the--indicator fill. There is no size prop and no loading state — a checkbox states intent inside a form, and the submit button owns the busyness.
Tri-state
checked takes true, false, or "indeterminate" — the Radix value passed straight through, so a select-all header row is one prop. The indeterminate box reports aria-checked="mixed". Grouping is the app's job: this component ships no checkbox group. Toggle the parent below to watch the mark morph.
Error
Pass error and the box mirrors Input: the border, the focus ring, and the label turn destructive, and the message renders below. The fill turns destructive too while checked. A 20px box is too small to read a red border against a blue fill, so the whole control carries one color.
You must accept the terms to continue
Accept the updated terms, revised today
You must accept the terms to continue
Accept the updated terms, revised today
Motion
The check scales in with opacity on spring-bounce at --motion-base. Entry cannot morph, because a morph needs two shapes. Checked to indeterminate is the signature move: the d attribute of one path animates between the check and the dash. Both marks are drawn as one move-to plus two line-tos, which is what lets motion interpolate them — keep the command counts identical if you redraw either mark. Unchecking fades and shrinks the mark out on spring-settle, with no reverse draw. The fill, the hover shade, the focus ring, and the destructive colors are CSS transitions at --motion-fast.
Accessibility
Checkbox generates the box id and wires the label htmlFor itself, so clicking the label toggles the box and the click target covers both. Tab focuses the box and Space toggles it. An error sets aria-invalid and links the message through aria-describedby, keeping any description you passed. The focus ring is keyboard-only and sits 2px clear of the box, the same 3px ring Button and Switch use. There is no press ring, because a held state means nothing on an instant toggle. required marks the label and sets aria-required; the validation itself stays with your app.