Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Checkbox

A binary (or tri-state) toggle — checked, unchecked, indeterminate, and disabled, built on Base UI Checkbox.

Status
stable
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/checkbox

Usage

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.

This field is required.
<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.

UncheckedCheckedIndeterminatesmdefault

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

PropTypeDefaultDescription
checkedbooleanWhether the checkbox is ticked (controlled). Pair with onCheckedChange. Use defaultChecked for an uncontrolled checkbox instead.
defaultCheckedbooleanfalseWhether the checkbox is initially ticked (uncontrolled).
disabledbooleanfalsePrevent the user from changing the checkbox while still submitting its value.
indeterminatebooleanfalseMixed 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.
renderComponentRenderFn<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

AttributeValues
data-sizemirrors 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.
  • indeterminate sets aria-checked="mixed"; checked/unchecked set aria-checked="true"/"false".
  • Always give it an accessible name — wrap it in a Field, use a sibling <label htmlFor> with nativeButton render={<button />}, or pass aria-label.
  • :focus-visible shows a 2px ring (outline-ring) — never outline: none. disabled removes it from the tab order.
KeyAction
TabMove focus to / from the checkbox.
SpaceToggle the checkbox checked / unchecked.
ContractStates tested
Behaviourdefault, checked, disabled, dragging, error, indeterminate, invalid
Accessibilitychecked, focus-visible, invalid, labeled, semantic-html
Visualdefault, hover, disabled, invalid, checked, error, dark

Do / Don't

Do
Give every checkbox a label — wrap it in a Field, or use nativeButton when pairing a sibling htmlFor label.
Don't
Use an htmlFor sibling label against the default span root, or use indeterminate as a third user-selectable value.

On this page