Checkbox
A binary (or tri-state) toggle — checked, unchecked, indeterminate, and disabled, built on Base UI Checkbox.
- Status
- Since
0.1.0- Accessibility pattern
- native checkbox
Last updated
Install
Add Checkbox from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/checkboxUsage
import { Checkbox } from "@/components/ui/checkbox";
<Checkbox aria-label="Accept terms" defaultChecked />;Built on Base UI Checkbox: it renders a styled
<span> plus a hidden <input> and a lucide check/minus indicator. Pair it with a label for
accessibility — either inside a Field (which auto-associates the label)
or by rendering a native button when you need a sibling <label htmlFor>. For a standalone checkbox
with no visible label, pass an aria-label.
Examples
With a Field
Field with orientation="horizontal" places the checkbox before an inline label and wires the
label association for you — no manual htmlFor/id.
import { Checkbox } from "@/components/ui/checkbox";
import { Field } from "@/components/ui/field";
<Field label="Subscribe to product updates" orientation="horizontal">
<Checkbox defaultChecked />
</Field>;Sibling label
Base UI's default checkbox root is a <span> so enclosing-label patterns work well. When your markup
needs a sibling <label htmlFor>, render the root as a native button so the id targets the visible
interactive element:
<label htmlFor="terms-checkbox">Accept terms</label>
<Checkbox id="terms-checkbox" nativeButton render={<button type="button" />} />States
checked/defaultChecked tick it; indeterminate renders the minus indicator and sets
aria-checked="mixed" (for a "select all" parent); disabled removes interaction. Every state shows
a :focus-visible ring on keyboard focus.
Sizes
md (size-4) and sm (size-3.5) — the icon scales with the box so checkboxes line up with
sibling inputs and switches.
<Checkbox size="sm" aria-label="Compact" />
<Checkbox size="md" aria-label="Default" />Invalid
Set aria-invalid (or place the checkbox inside a Field with an error)
to show the destructive border. A Field error also renders a polite role="status" message for
assistive tech, and the whole field shakes once on the transition — the motion belongs to Field,
not to the checkbox.
<Checkbox aria-invalid aria-label="Invalid checkbox" />
<Field label="Accept the terms to continue" orientation="horizontal" error="This field is required.">
<Checkbox />
</Field>Size × state matrix
Because the indicator icon scales with the box, the sm and default sizes read distinctly across
every state — unchecked, checked, and indeterminate.
Playground
Try both sizes with the disabled and indeterminate states, then copy the generated JSX.
<Field label="Accept terms" orientation="horizontal">
<Checkbox />
</Field>API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | — | Whether the checkbox is ticked (controlled). Pair with onCheckedChange.
Use defaultChecked for an uncontrolled checkbox instead. |
defaultChecked | boolean | false | Whether the checkbox is initially ticked (uncontrolled). |
disabled | boolean | false | Prevent the user from changing the checkbox while still submitting its value. |
indeterminate | boolean | false | Mixed state — neither ticked nor unticked. Renders the minus indicator and
sets aria-checked="mixed". Typically derived from a group of children. |
onCheckedChange | ((checked: boolean, eventDetails: BaseCheckbox.Root.ChangeEventDetails) => void) | — | Called when the checkbox is ticked or unticked, with the next checked value. |
render | ComponentRenderFn<HTMLProps, CheckboxRootState> | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | — | Replace the rendered element via Base UI render composition. Pass a
ReactElement or a render function — Base UI merges this
wrapper's className, data-slot, and state data-* onto your element and
forwards the ref. The element must support role="checkbox" semantics. |
size | "md" | "sm" | — |
Data attributes and CSS variables on Checkbox
| Attribute | Values |
|---|---|
data-size | mirrors a prop or state value |
data-slot | "checkbox" | "checkbox-indicator" |
Accessibility
- Renders a native checkbox (
role="checkbox") with a hidden<input>for form submission; supports controlled (checked) and uncontrolled (defaultChecked) use. indeterminatesetsaria-checked="mixed"; checked/unchecked setaria-checked="true"/"false".- Always give it an accessible name — wrap it in a
Field, use a sibling<label htmlFor>withnativeButton render={<button />}, or passaria-label. :focus-visibleshows a 2px ring (outline-ring) — neveroutline: none.disabledremoves it from the tab order.
| Key | Action |
|---|---|
| Tab | Move focus to / from the checkbox. |
| Space | Toggle the checkbox checked / unchecked. |
| Contract | States tested |
|---|---|
| Behaviour | default, checked, disabled, dragging, error, indeterminate, invalid |
| Accessibility | checked, focus-visible, invalid, labeled, semantic-html |
| Visual | default, hover, disabled, invalid, checked, error, dark |