Skip to content
Component installs need the registry setup
VegaStack Design

Checkbox

A binary (or indeterminate) toggle built on Base UI Checkbox, with a 24px invisible hit area.

Status
stable
Since
0.1.0
Accessibility pattern
APG checkbox

Last updated

Install

Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Checkbox before installation. Retain the digest printed by this command.

pnpm exec vegastack-design verify checkbox

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/checkbox

Run the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.

Usage

import { Checkbox } from "@/components/ui/checkbox";

<Checkbox />;

Examples

Checked State

Use defaultChecked for an uncontrolled checkbox, or checked with onCheckedChange to control it. indeterminate draws the mixed state a "select all" parent needs, with a minus in place of the check so it never reads as checked.

const [checked, setChecked] = React.useState(false);

<Checkbox checked={checked} onCheckedChange={setChecked} />;

Shape

shape is square (the default) or circle. Use circle for a check that marks something done — a task or a to-do row; keep square for selection (bulk-select, "select all") and form fields.

<Checkbox shape="circle" aria-label="Mark Send the quote done" />

Invalid State

Set aria-invalid on the Checkbox and data-invalid on the Field wrapper. The destructive border holds in every state, focused or not (FOC-14).

Basic

Pair the checkbox with Field and FieldLabel for layout and labelling.

Description

Use FieldContent and FieldDescription for helper text below the label.

By clicking this checkbox, you agree to the terms and conditions.

Disabled

Use disabled to prevent interaction; the control dims on its own, inside a Field or not. Add data-disabled to the Field so the label dims with it.

Group

Several Fields inside a FieldSet make a checkbox list with a legend and a description.

Show these items on the desktop:

Select the items you want to show on the desktop.

Table

A header checkbox that reports indeterminate while some — but not all — rows are selected.

NameEmail
Sarah Chensarah.chen@example.com
Marcus Rodriguezmarcus.r@example.com
Priya Patelpriya.patel@example.com

RTL

Layout is written in logical properties, so the checkbox and its label swap sides in a right-to-left document.

By clicking this checkbox, you agree to the terms.

بالنقر على هذا المربع، فإنك توافق على الشروط.

States

Rest, checked, indeterminate, invalid and disabled in one frame.

Playground

Try the disabled, indeterminate and invalid states, then copy the generated JSX.

<Field orientation="horizontal">
  <Checkbox id="terms" />
  <FieldLabel htmlFor="terms">Accept terms</FieldLabel>
</Field>

API Reference

PropTypeDefaultDescription
shape"circle" | "square"—circle marks something done (a task, a to-do); square selects and fills in forms.

Accessibility

  • Renders Base UI's Checkbox: a <span role="checkbox"> root carrying aria-checked ("mixed" when indeterminate) and a visually hidden native <input type="checkbox"> that takes part in the form; Space toggles it.
  • The mixed state has its own glyph, a minus, so it is distinguishable from checked without reading aria-checked (A11Y-19).
  • The visible box is 16px, so an ::after pseudo-element extends the pointer target to at least 24×24 (A11Y-2), verified by a real elementFromPoint probe.
  • On :focus-visible the global 2px outline in base.css applies (FOC-1). The recipe carries no ring of its own and no outline-none (FOC-6).
  • A bare Checkbox has no visible text — give it an aria-label, or a FieldLabel bound with htmlFor, as every example here does.
ContractStates tested
Behaviourdefault, checked, indeterminate, disabled, invalid
Accessibilitynative-or-base-ui-semantics, browser-accessibility-test, checked, invalid, disabled
Visualdefault, checked, indeterminate, invalid, disabled

Do / Don't

Do
Use a Checkbox for an option that is submitted with a form, and indeterminate for a partially-selected parent.
Don't
Use a Checkbox for a setting that applies instantly — that is a Switch.

Deviations

Upstream's file plus packages/ui/upstream/patches/checkbox.patch. Every hunk:

  • FOC-1, FOC-6 — the outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 glow is removed, along with the aria-invalid halo rings. base.css owns the single :focus-visible outline, and outline-none would suppress it.
  • FOC-14 — the invalid tint and its checked counterpart stay upstream's aria-invalid:border-destructive and aria-invalid:aria-checked:border-primary, held in every state, focused or not (FOC-14 retired FOC-5's not-focus: guard).
  • FOC-12 — upstream rings the whole choice card and cancels the control's own ring with three group-has-[:focus-visible]/field-label: rules. This system rings the control, so all three are removed with the card ring they existed to serve.
  • A11Y-19 — the indicator renders through Base UI's render callback and draws MinusIcon when the state is indeterminate, CheckIcon otherwise. Upstream always draws the check, so a mixed box looked checked.
  • FRM-15 — disabled:cursor-not-allowed disabled:opacity-50 become data-disabled:…. Base UI renders the root as a <span>, which :disabled never matches, so upstream's checkbox dimmed only inside a disabled Field; keyed on the data-disabled Base UI sets, it dims everywhere.
  • DOC-2 — cn is imported from @vegastack/design.
  • DOC-1 — prettier reflow plus the registry-stamp.mjs provenance header the three-copy model needs; no decision of its own.
  • A11Y-24 — the invisible hit area grows to 44×44 on a coarse pointer. Widened by MK, 2026-10-26.

On this page