1
ComponentsForm Controls

Checkbox Group

Checkbox Group lets users select any number of options from a related set.

Purpose

Checkbox Group lets users select any number of options from a related set. Use Radio Group when exactly one option may be selected.

Grouped choices

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

export function Example() {
  return (
    <CheckboxGroup aria-label="Notification channels" defaultValue={["email"]}>
      <Label><Checkbox value="email" />Email</Label>
      <Label><Checkbox value="sms" />SMS</Label>
    </CheckboxGroup>
  );
}

Installation

With the Siteplane registry alias configured, add the component and import the installed source from your app.

CLI

pnpm dlx shadcn@4.16.1 add @siteplane/checkbox-group

Import

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

Need the machine-readable source metadata? View registry JSON.

Usage

Use Checkbox Group to keep related multi-select choices together while each Checkbox owns its checked state.

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

export function Example() {
  return (
    <CheckboxGroup aria-label="Notification channels" defaultValue={["email"]}>
      <Label><Checkbox value="email" />Email</Label>
      <Label><Checkbox value="sms" />SMS</Label>
    </CheckboxGroup>
  );
}

Examples

States, field wiring, select-all, layout overrides, form integration, group sizes and option descriptions are compositions of the same CheckboxGroup and Checkbox primitives.

States

{/* Disabled */}
<CheckboxGroup aria-label="Disabled notification channels" defaultValue={["email"]} disabled>
  <Label><Checkbox value="email" />Email</Label>
  <Label><Checkbox value="sms" />SMS</Label>
</CheckboxGroup>

{/* Invalid */}
<CheckboxGroup aria-invalid aria-label="Invalid notification channels">
  <Label><Checkbox aria-invalid value="email" />Email</Label>
  <Label><Checkbox aria-invalid value="sms" />SMS</Label>
</CheckboxGroup>

Field with error

const labelId = useId();

<Field>
  <FieldLabel id={labelId}>Required scopes</FieldLabel>
  <CheckboxGroup aria-invalid aria-labelledby={labelId}>
    <Label><Checkbox aria-invalid value="read" />Read</Label>
    <Label><Checkbox aria-invalid value="write" />Write</Label>
  </CheckboxGroup>
  <FieldError>Select at least one scope.</FieldError>
</Field>

Select all with indeterminate parent

const scopes = ["read", "comment", "write"];
const [value, setValue] = useState<string[]>(["read", "comment"]);

const allChecked = value.length === scopes.length;
const someChecked = value.length > 0 && !allChecked;

<Label className="font-medium">
  <Checkbox
    checked={allChecked}
    indeterminate={someChecked}
    onCheckedChange={(checked) => setValue(checked ? [...scopes] : [])}
  />
  Project access
</Label>
<CheckboxGroup
  aria-label="Project permissions"
  className="pl-6"
  onValueChange={setValue}
  value={value}
>
  <Label><Checkbox value="read" />Read</Label>
  <Label><Checkbox value="comment" />Comment</Label>
  <Label><Checkbox value="write" />Write</Label>
</CheckboxGroup>

Horizontal group

<CheckboxGroup
  aria-label="Weekdays"
  className="flex-row flex-wrap gap-4"
  defaultValue={["mon", "wed"]}
>
  <Label><Checkbox value="mon" />Mon</Label>
  <Label><Checkbox value="wed" />Wed</Label>
  <Label><Checkbox value="fri" />Fri</Label>
</CheckboxGroup>

Card style options

<CheckboxGroup aria-label="Workspace add-ons" defaultValue={["analytics"]}>
  <Label className="flex w-full items-center justify-between gap-6 rounded-lg border p-3 hover:bg-accent/50 has-data-checked:border-primary/48 has-data-checked:bg-accent/50">
    <span className="grid gap-1">
      <span>Analytics</span>
      <span className="text-muted-foreground text-xs">Dashboards and usage reports.</span>
    </span>
    <Checkbox value="analytics" />
  </Label>
  <Label className="flex w-full items-center justify-between gap-6 rounded-lg border p-3 hover:bg-accent/50 has-data-checked:border-primary/48 has-data-checked:bg-accent/50">
    <span className="grid gap-1">
      <span>Audit log</span>
      <span className="text-muted-foreground text-xs">Records security-relevant events.</span>
    </span>
    <Checkbox value="audit" />
  </Label>
</CheckboxGroup>

Form integration

Scopes: not submitted
const labelId = useId();
const [loading, setLoading] = useState(false);
const [status, setStatus] = useState("Scopes: not submitted");

async function onSubmit(event: FormEvent<HTMLFormElement>) {
  event.preventDefault();
  const formData = new FormData(event.currentTarget);
  setLoading(true);
  await new Promise((resolve) => setTimeout(resolve, 800));
  setLoading(false);
  const scopes = formData.getAll("scopes").map(String);
  setStatus(`Scopes: ${scopes.length > 0 ? scopes.join(", ") : "none"}`);
}

<Form onSubmit={onSubmit}>
  <Field name="scopes">
    <FieldLabel id={labelId}>Choose scopes</FieldLabel>
    <CheckboxGroup aria-labelledby={labelId} defaultValue={["read"]}>
      <Label><Checkbox name="scopes" value="read" />Read</Label>
      <Label><Checkbox name="scopes" value="write" />Write</Label>
    </CheckboxGroup>
  </Field>
  <Button loading={loading} type="submit">Submit</Button>
  <span>{status}</span>
</Form>

Sizes

<CheckboxGroup aria-label="Notification channels" size="sm">
  <Label><Checkbox value="email" />Email</Label>
  <Label><Checkbox value="sms" />SMS</Label>
</CheckboxGroup>

{/* size is inherited by every Checkbox in the group */}
<CheckboxGroup aria-label="Notification channels" size="xl">...</CheckboxGroup>

With descriptions

Every production deploy and rollback.

Paging alerts and postmortem updates.

<CheckboxGroup aria-label="Deployment notifications" defaultValue={["deploys"]}>
  <Field>
    <Label><Checkbox value="deploys" />Deployments</Label>
    <FieldDescription>Every production deploy and rollback.</FieldDescription>
  </Field>
  <Field>
    <Label><Checkbox value="incidents" />Incidents</Label>
    <FieldDescription>Paging alerts and postmortem updates.</FieldDescription>
  </Field>
</CheckboxGroup>

API Reference

The reference lists the source-owned exports and props that are easy to miss in visual examples. Inherited Base UI props remain available unless the wrapper narrows them.

  • Give each child Checkbox a stable value; group value, defaultValue, and onValueChange use arrays.
  • allValues enables the first-class select-all contract. Combine it with <Checkbox parent /> to derive checked and indeterminate state and toggle all children without manual synchronization.
  • Use size on the group to size its children, with a local Checkbox size taking precedence.

Motion

Component motion uses the shared Siteplane motion contract. See the global motion guide for provider setup, tokens and reduced-motion behavior.

  • Check, indeterminate, border, focus, and reduced-motion behavior comes from Checkbox.
  • Selection must not change row or card dimensions.

Accessibility

  • Name every group through visible context, aria-label, or aria-labelledby.
  • Pair every Checkbox with visible clickable label text or an accessible label.
  • Put invalid state on the group and render a visible FieldError.
  • The parent Checkbox must communicate mixed state when only some children are selected.

Implementation Guidance

  • Always group related multi-select options in Checkbox Group.
  • Use allValues and Checkbox parent for select-all behavior instead of rebuilding synchronization with local state.
  • Use Field for group labels, descriptions, and validation.

On This Page