Checkbox
A binary (or indeterminate) choice, typically submitted with a form.
@garn/ui/checkboxDefault
Default checkbox.
Sizing
Box size, mapped to the fixed --garn-box-* geometry (identity, not density).
States
Interactive states, toggled live.
Labeled
Checkbox with a clickable label.
Indeterminate
The third state.
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add checkbox- Registry
checkbox- Deps
@radix-ui/react-checkboxclass-variance-authoritylucide-react- Registry deps
utils
Import
import { Checkbox } from "@garn/ui/checkbox";Props
The component's public props and their types.
size- Description
- No description.
- Type
"lg" | "md" | "sm"- Default
md
Plus 294 inherited native <button> attributes.
Styling
Target these data-slots and states, and remap these tokens, to restyle without forking the component.
[data-slot="checkbox"]States
| Selector | State | Description |
|---|---|---|
[data-state=checked] | checked | Filled with --garn-brand-solid. |
[data-state=indeterminate] | indeterminate | |
:focus-visible | focus-visible | 2px ring + offset. |
[disabled] | disabled | Dims and blocks pointer. |
| Property | Token | Tier |
|---|---|---|
size | --garn-box-md | semantic · identity |
fill | --garn-brand-solidbrandable | semantic |
ring | --garn-ringbrandable | semantic |
When to use
Toggle an independent boolean, or select several items from a list.
Reach for something else when
- An on/off that takes effect immediately (use a switch)
- one choice among mutually exclusive options (use radio-group).
Overview
Checkbox toggles a single independent boolean ("Remember me", "I agree") or selects several items from a list where choices aren't mutually exclusive. It supports an indeterminate state for a parent that summarizes a partially-selected group. Reach for RadioGroup instead when exactly one of a few options must be chosen, or Switch when the change applies immediately with no Save step.
Guidelines
Pair every checkbox with a Label. The box alone is a tiny hit target with no accessible name; an associated Label both names it and extends the click area to the text. Keep the wording affirmative so checking it means "yes" — "Send me updates", not "Don't send updates".
Use indeterminate only for a summary parent. It's the visual for "some but not all children selected" in a select-all pattern — never a third user-settable value. Drive it from the children's state; clicking it should resolve to all-on or all-off.
Don't make a checkbox apply instantly. Checkboxes imply a pending choice confirmed on submit; if the toggle takes effect the moment it changes (a setting), that's a Switch. Group related checkboxes under a shared heading or fieldset so the relationship is announced.
Best practices
- Pair with a clickable label and keep the hit target generous.
- Use the
indeterminatestate for a parent that summarizes a partially-checked group.
- Don't use a checkbox for a setting that applies instantly — that's a switch.
- Don't group mutually exclusive options as checkboxes — use radio-group.
Content guidelines
- Phrase the label as the positive choice the user opts into (“Email me about updates”).
- Use sentence case, keep it to a short phrase, and make the whole label clickable.
- In a list, keep labels parallel and ordered so the set is easy to scan.
Troubleshooting
The checkbox won't change when clicked.
Cause. Passing checked without onCheckedChange makes it controlled and frozen.
Fix. Use defaultChecked for uncontrolled, or pass checked + onCheckedChange.
Accessibility
- Role
checkbox- ARIA APG
- checkbox
- Focus
- outline-hidden + 2px ring with a 2px offset.
Accessibility requirements
Give every checkbox an accessible label (a clickable <label htmlFor>, or aria-label).
when no associated <label> or aria-label/aria-labelledby
Keyboard
| Space | Toggle checked / unchecked. |
- data-state reflects checked / unchecked / indeterminate (Radix maps it to aria-checked).