Checkbox
A binary (or indeterminate) toggle built on Base UI Checkbox, with a 24px invisible hit area.
- Status
- 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 checkboxInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/checkboxRun 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.
Table
A header checkbox that reports indeterminate while some — but not all — rows are selected.
| Name | ||
|---|---|---|
| Sarah Chen | sarah.chen@example.com | |
| Marcus Rodriguez | marcus.r@example.com | |
| Priya Patel | priya.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
| Prop | Type | Default | Description |
|---|---|---|---|
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 carryingaria-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
::afterpseudo-element extends the pointer target to at least 24×24 (A11Y-2), verified by a realelementFromPointprobe. - On
:focus-visiblethe global 2px outline inbase.cssapplies (FOC-1). The recipe carries no ring of its own and nooutline-none(FOC-6). - A bare Checkbox has no visible text — give it an
aria-label, or aFieldLabelbound withhtmlFor, as every example here does.
| Contract | States tested |
|---|---|
| Behaviour | default, checked, indeterminate, disabled, invalid |
| Accessibility | native-or-base-ui-semantics, browser-accessibility-test, checked, invalid, disabled |
| Visual | default, checked, indeterminate, invalid, disabled |
Do / Don't
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/50glow is removed, along with thearia-invalidhalo rings.base.cssowns the single:focus-visibleoutline, andoutline-nonewould suppress it. - FOC-14 — the invalid tint and its checked counterpart stay upstream's
aria-invalid:border-destructiveandaria-invalid:aria-checked:border-primary, held in every state, focused or not (FOC-14 retired FOC-5'snot-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
rendercallback and drawsMinusIconwhen the state is indeterminate,CheckIconotherwise. Upstream always draws the check, so a mixed box looked checked. - FRM-15 —
disabled:cursor-not-allowed disabled:opacity-50becomedata-disabled:…. Base UI renders the root as a<span>, which:disablednever matches, so upstream's checkbox dimmed only inside a disabledField; keyed on thedata-disabledBase UI sets, it dims everywhere. - DOC-2 —
cnis imported from@vegastack/design. - DOC-1 — prettier reflow plus the
registry-stamp.mjsprovenance 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.