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 Group

Shared state for a set of checkboxes, with first-class "select all" — parent, mixed, and the whole-set toggle, built on Base UI Checkbox Group.

Status
stable
Since
0.7.0
Accessibility pattern
grouped checkboxes

Last updated

Permissions

Install

Add Checkbox Group from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/checkbox-group

Usage

import { Checkbox } from "@/components/ui/checkbox";
import { CheckboxGroup } from "@/components/ui/checkbox-group";
import { Label } from "@/components/ui/label";

const ALL = ["read", "write", "admin"];

<CheckboxGroup defaultValue={["read"]} allValues={ALL} aria-labelledby="perms">
  <Label>
    <Checkbox parent /> All permissions
  </Label>
  <Label>
    <Checkbox value="read" /> Read
  </Label>
  <Label>
    <Checkbox value="write" /> Write
  </Label>
  <Label>
    <Checkbox value="admin" /> Admin
  </Label>
</CheckboxGroup>;

Built on Base UI Checkbox Group. The group owns the array of ticked values; each child is a plain Checkbox carrying a value. There is no CheckboxGroupItem — a wrapper whose only job is to forward every prop would earn nothing.

Pass allValues and mark one child parent to get the select-all row. Base UI ticks and unticks the whole set from it and drives its mixed (indeterminate) state when only some children are on — the arithmetic that used to be written by hand on every settings page and in every grid header.

Name the group. It renders no label of its own, so give it a wrapping FieldSet + FieldLegend (the strongest form, and the one to prefer in a form) or an aria-labelledby pointing at a heading. An unnamed group announces as a bare list of checkboxes with no idea what they belong to.

Anatomy

<FieldSet>
  <FieldLegend>Permissions</FieldLegend>
  <CheckboxGroup value={value} onValueChange={setValue} allValues={ALL}>
    <Label>
      <Checkbox parent /> All permissions
    </Label>
    <Label>
      <Checkbox value="read" /> Read
    </Label>
  </CheckboxGroup>
</FieldSet>
CheckboxGroup — data-slot="checkbox-group"

Examples

Select all

The parent ticks and unticks every value in allValues, and reads mixed while only some children are on.

Permissions

States

Nothing ticked, some ticked, all ticked — the parent's own state is derived, never assigned.

Nothing ticked
Some ticked — the parent is mixed
All ticked

Uncontrolled

Omit value/onValueChange and pass defaultValue. Without allValues there is no parent, which is the right shape for a plain list of independent options.

Notifications

Disabled

disabled on the group disables every child, the parent included. The boxes stay hoverable so a Tooltip can explain why they are unavailable.

Permissions

API Reference

PropTypeDefaultDescription
allValuesstring[]Every value in the group. Set it to enable a parent checkbox: the parent ticks and unticks the whole set and shows the mixed (indeterminate) state when only some children are on. Without it a parent checkbox has nothing to compute against.
defaultValuestring[]Values ticked initially (uncontrolled). Use value instead for a controlled group.
disabledbooleanfalseDisables the whole group, every child included.
onValueChange((value: string[], eventDetails: BaseCheckboxGroup.ChangeEventDetails) => void)Called with the next array of ticked values whenever a child is toggled, plus Base UI's event details.
valuestring[]Values of the checkboxes that are ticked (controlled). Pair with onValueChange. Required — along with allValues — for a parent checkbox to work.

Data attributes and CSS variables on CheckboxGroup

AttributeValues
data-slot"checkbox-group"

Accessibility

  • The group renders a <div> with no implicit name. Wrap it in a FieldSet/FieldLegend, or give it aria-labelledby.
  • The parent checkbox exposes aria-checked="mixed" while only some children are ticked, and "true"/"false" at the ends.
  • Every checkbox is its own tab stop — a checkbox group is not a roving-tabindex collection, unlike a RadioGroup, because the options are not mutually exclusive.
  • disabled on the group removes every child from the tab order.
KeyAction
TabMove focus to the next checkbox in the group.
SpaceToggle the focused checkbox — or, on the parent, the whole set.
ContractStates tested
Behaviourdefault, checked, disabled, indeterminate
Accessibilitychecked, focus-visible, labeled, semantic-html
Visualdefault, disabled, dark

Do / Don't

Do
Name the group with a FieldSet and FieldLegend, and let allValues plus a parent child do the select-all arithmetic.
Don't
Compute the parent's checked and indeterminate state in your own component state, or use a checkbox group for mutually-exclusive choices.

On this page