Checkbox Group
Provides shared state to a series of checkboxes. Built with Base UI and Tailwind CSS. Copy-paste ready.
import { Checkbox } from "@/components/ui/checkbox";
import { CheckboxGroup } from "@/components/ui/checkbox-group";
import { Label } from "@/components/ui/label";
export default function Particle() {
return (
<CheckboxGroup aria-label="Select frameworks" defaultValue={["next"]}>
<Label>
<Checkbox value="next" />
Next.js
</Label>
<Label>
<Checkbox value="vite" />
Vite
</Label>
<Label>
<Checkbox value="astro" />
Astro
</Label>
</CheckboxGroup>
);
}
Installation
pnpm dlx shadcn@latest add @cnippet/checkbox-group
Usage
import { Checkbox } from "@/components/ui/checkbox"
import { CheckboxGroup } from "@/components/ui/checkbox-group"<CheckboxGroup>
<Label>
<Checkbox defaultChecked />
Next.js
</Label>
<Label>
<Checkbox />
Vite
</Label>
<Label>
<Checkbox />
Astro
</Label>
</CheckboxGroup>Examples
For accessible group labelling and validation, prefer wrapping checkbox groups with Field and Fieldset. See the related example: Checkbox group field.
With Disabled Item
Demonstrates how to disable a single option within a group while leaving the others interactive.
import { Checkbox } from "@/components/ui/checkbox";
import { CheckboxGroup } from "@/components/ui/checkbox-group";
import { Label } from "@/components/ui/label";
export default function Particle() {
return (
<CheckboxGroup aria-label="Select frameworks" defaultValue={["next"]}>
<Label>
<Checkbox value="next" />
Next.js
</Label>
<Label>
<Checkbox disabled value="vite" />
Vite
</Label>
<Label>
<Checkbox value="astro" />
Astro
</Label>
</CheckboxGroup>
);
}
Parent Checkbox
A "select all" parent checkbox whose state reflects the group — unchecked, checked, or indeterminate depending on child selections.
"use client";
import { useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { CheckboxGroup } from "@/components/ui/checkbox-group";
import { Label } from "@/components/ui/label";
const frameworks = [
{ id: "next", name: "Next.js" },
{ id: "vite", name: "Vite" },
{ id: "astro", name: "Astro" },
];
export default function Particle() {
const [value, setValue] = useState<string[]>([]);
return (
<CheckboxGroup
allValues={frameworks.map((framework) => framework.id)}
aria-labelledby="frameworks-caption"
onValueChange={setValue}
value={value}
>
<Label id="frameworks-caption">
<Checkbox name="frameworks" parent />
Frameworks
</Label>
{frameworks.map((framework) => (
<Label className="ms-4" key={framework.id}>
<Checkbox value={framework.id} />
{framework.name}
</Label>
))}
</CheckboxGroup>
);
}
Nested Parent Checkbox
Multi-level parent/child checkboxes where each parent controls a subset, and a root parent controls all subsets.
"use client";
import { useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { CheckboxGroup } from "@/components/ui/checkbox-group";
import { Label } from "@/components/ui/label";
const mainPermissions = [
{ id: "view-dashboard", name: "View Dashboard" },
{ id: "manage-users", name: "Manage Users" },
{ id: "access-reports", name: "Access Reports" },
];
const userManagementPermissions = [
{ id: "create-user", name: "Create User" },
{ id: "edit-user", name: "Edit User" },
{ id: "delete-user", name: "Delete User" },
{ id: "assign-roles", name: "Assign Roles" },
];
export default function Particle() {
const [mainValue, setMainValue] = useState<string[]>([]);
const [managementValue, setManagementValue] = useState<string[]>([]);
const managementIsPartial =
managementValue.length > 0 &&
managementValue.length !== userManagementPermissions.length;
return (
<CheckboxGroup
allValues={mainPermissions.map((p) => p.id)}
aria-labelledby="user-permissions-caption"
onValueChange={(value) => {
if (value.includes("manage-users")) {
setManagementValue(userManagementPermissions.map((p) => p.id));
} else if (
managementValue.length === userManagementPermissions.length
) {
setManagementValue([]);
}
setMainValue(value);
}}
value={mainValue}
>
<Label id="user-permissions-caption">
<Checkbox indeterminate={managementIsPartial} parent />
User Permissions
</Label>
{mainPermissions
.filter((p) => p.id !== "manage-users")
.map((p) => (
<Label className="ms-4" key={p.id}>
<Checkbox value={p.id} />
{p.name}
</Label>
))}
<CheckboxGroup
allValues={userManagementPermissions.map((p) => p.id)}
aria-labelledby="manage-users-caption"
className="ms-4"
onValueChange={(value) => {
if (value.length === userManagementPermissions.length) {
setMainValue((prev) =>
Array.from(new Set([...prev, "manage-users"])),
);
} else {
setMainValue((prev) => prev.filter((v) => v !== "manage-users"));
}
setManagementValue(value);
}}
value={managementValue}
>
<Label id="manage-users-caption">
<Checkbox parent />
Manage Users
</Label>
{userManagementPermissions.map((p) => (
<Label className="ms-4" key={p.id}>
<Checkbox value={p.id} />
{p.name}
</Label>
))}
</CheckboxGroup>
</CheckboxGroup>
);
}
Form Integration
Wraps the checkbox group in Field and Form for built-in validation and required-state support.
"use client";
import type { FormEvent } from "react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { CheckboxGroup } from "@/components/ui/checkbox-group";
import { Field, FieldItem, FieldLabel } from "@/components/ui/field";
import { Fieldset, FieldsetLegend } from "@/components/ui/fieldset";
import { Form } from "@/components/ui/form";
export default function Particle() {
const [loading, setLoading] = useState(false);
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
setLoading(true);
await new Promise((r) => setTimeout(r, 800));
setLoading(false);
const frameworks = formData.getAll("frameworks") as string[];
alert(`Selected: ${frameworks.join(", ") || "none"}`);
};
return (
<Form className="flex w-full max-w-40 flex-col gap-4" onSubmit={onSubmit}>
<Field name="frameworks" render={(props) => <Fieldset {...props} />}>
<FieldsetLegend className="font-medium text-sm">
Frameworks
</FieldsetLegend>
<CheckboxGroup defaultValue={["next"]}>
<FieldItem>
<FieldLabel>
<Checkbox value="next" />
Next.js
</FieldLabel>
</FieldItem>
<FieldItem>
<FieldLabel>
<Checkbox value="vite" />
Vite
</FieldLabel>
</FieldItem>
<FieldItem>
<FieldLabel>
<Checkbox value="astro" />
Astro
</FieldLabel>
</FieldItem>
</CheckboxGroup>
</Field>
<Button loading={loading} type="submit">
Submit
</Button>
</Form>
);
}
Two-Level Tech Stack Picker
Two CheckboxGroup parent checkboxes (Frontend, Backend) each controlling a subset of items, inside a shared outer group that handles the select-all root.
Tech stack interests
"use client";
import { useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { CheckboxGroup } from "@/components/ui/checkbox-group";
import { Label } from "@/components/ui/label";
const CATEGORIES = [
{
id: "frontend",
items: [
{ id: "react", label: "React" },
{ id: "vue", label: "Vue" },
{ id: "svelte", label: "Svelte" },
],
label: "Frontend",
},
{
id: "backend",
items: [
{ id: "node", label: "Node.js" },
{ id: "python", label: "Python" },
{ id: "go", label: "Go" },
],
label: "Backend",
},
];
export function Pattern() {
const [values, setValues] = useState<Record<string, string[]>>(
Object.fromEntries(CATEGORIES.map((c) => [c.id, []])),
);
const allSelected = CATEGORIES.flatMap((c) => values[c.id] ?? []);
return (
<div className="w-full max-w-xs space-y-4">
<p className="font-semibold text-sm">Tech stack interests</p>
<div className="space-y-4">
{CATEGORIES.map((cat) => (
<CheckboxGroup
allValues={cat.items.map((i) => i.id)}
key={cat.id}
onValueChange={(v) =>
setValues((prev) => ({ ...prev, [cat.id]: v }))
}
value={values[cat.id] ?? []}
>
<Label className="font-medium text-sm">
<Checkbox name={cat.id} parent />
{cat.label}
</Label>
{cat.items.map((item) => (
<Label
className="ms-5 text-muted-foreground text-sm"
key={item.id}
>
<Checkbox value={item.id} />
{item.label}
</Label>
))}
</CheckboxGroup>
))}
</div>
{allSelected.length > 0 && (
<p className="text-muted-foreground text-xs">
Selected: {allSelected.join(", ")}
</p>
)}
</div>
);
}
Recurring Schedule
A day-of-week selector with separate parent checkboxes for Weekdays and Weekend, and a day count badge that reflects the current selection.
Recurring schedule
5 days/wk"use client";
import { useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Checkbox } from "@/components/ui/checkbox";
import { CheckboxGroup } from "@/components/ui/checkbox-group";
import { Label } from "@/components/ui/label";
const DAYS = [
{ id: "mon", label: "Mon" },
{ id: "tue", label: "Tue" },
{ id: "wed", label: "Wed" },
{ id: "thu", label: "Thu" },
{ id: "fri", label: "Fri" },
{ id: "sat", label: "Sat" },
{ id: "sun", label: "Sun" },
];
const WEEKDAYS = DAYS.slice(0, 5).map((d) => d.id);
const WEEKEND = DAYS.slice(5).map((d) => d.id);
export function Pattern() {
const [value, setValue] = useState<string[]>(WEEKDAYS);
const weekdayValues = value.filter((v) => WEEKDAYS.includes(v));
const weekendValues = value.filter((v) => WEEKEND.includes(v));
function updateSubValues(subIds: string[], newSubValues: string[]) {
setValue((prev) => [
...prev.filter((v) => !subIds.includes(v)),
...newSubValues,
]);
}
return (
<div className="w-full max-w-xs space-y-4">
<div className="flex items-center justify-between">
<p className="font-semibold text-sm">Recurring schedule</p>
<Badge size="sm" variant="secondary">
{value.length} days/wk
</Badge>
</div>
<CheckboxGroup
allValues={DAYS.map((d) => d.id)}
onValueChange={setValue}
value={value}
>
<Label className="font-medium text-muted-foreground text-sm">
<Checkbox parent />
Select all
</Label>
<div className="mt-1 ml-1 space-y-1 border-l pl-4">
<CheckboxGroup
allValues={WEEKDAYS}
onValueChange={(v) => updateSubValues(WEEKDAYS, v)}
value={weekdayValues}
>
<Label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
<Checkbox parent />
Weekdays
</Label>
{DAYS.slice(0, 5).map((d) => (
<Label className="ms-5 text-sm" key={d.id}>
<Checkbox value={d.id} />
{d.label}
</Label>
))}
</CheckboxGroup>
<CheckboxGroup
allValues={WEEKEND}
onValueChange={(v) => updateSubValues(WEEKEND, v)}
value={weekendValues}
>
<Label className="mt-2 font-medium text-muted-foreground text-xs uppercase tracking-wide">
<Checkbox parent />
Weekend
</Label>
{DAYS.slice(5).map((d) => (
<Label className="ms-5 text-sm" key={d.id}>
<Checkbox value={d.id} />
{d.label}
</Label>
))}
</CheckboxGroup>
</div>
</CheckboxGroup>
</div>
);
}
Integration Enablement
Integration options rendered as card rows with an "Enable all" parent checkbox at the top, and a Save button showing the active count.
Enable integrations
Connect tools your team already uses.
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { CheckboxGroup } from "@/components/ui/checkbox-group";
import { Label } from "@/components/ui/label";
const INTEGRATIONS = [
{ desc: "Sync repos and pull requests.", id: "github", label: "GitHub" },
{ desc: "Import issues and sprints.", id: "jira", label: "Jira" },
{ desc: "Post updates to channels.", id: "slack", label: "Slack" },
{ desc: "Embed design files in docs.", id: "figma", label: "Figma" },
{ desc: "Sync pages and databases.", id: "notion", label: "Notion" },
];
export function Pattern() {
const [value, setValue] = useState<string[]>(["github"]);
return (
<div className="w-full max-w-sm space-y-3">
<div>
<p className="font-semibold text-sm">Enable integrations</p>
<p className="mt-0.5 text-muted-foreground text-xs">
Connect tools your team already uses.
</p>
</div>
<CheckboxGroup
allValues={INTEGRATIONS.map((i) => i.id)}
onValueChange={setValue}
value={value}
>
<Label className="mb-1 text-muted-foreground text-sm">
<Checkbox parent />
Enable all
</Label>
{INTEGRATIONS.map((intg) => (
<label
className="flex cursor-pointer items-start gap-3 rounded-lg border px-3 py-2.5 transition-colors hover:bg-muted/50"
htmlFor={intg.id}
key={intg.id}
>
<Checkbox className="mt-0.5" id={intg.id} value={intg.id} />
<div>
<span className="font-medium text-sm">{intg.label}</span>
<p className="mt-0.5 text-muted-foreground text-xs">
{intg.desc}
</p>
</div>
</label>
))}
</CheckboxGroup>
<Button className="w-full" size="sm">
Save integrations ({value.length})
</Button>
</div>
);
}
Data Export Selector
Two section groups (Content, Settings) with nested items under each parent checkbox, plus an item count summary line below the list.
Export data
Choose what to include in your export.
0 of 6 items selected
"use client";
import { useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { CheckboxGroup } from "@/components/ui/checkbox-group";
import { Label } from "@/components/ui/label";
const EXPORT_SECTIONS = [
{
id: "content",
items: [
{ id: "posts", label: "Blog posts" },
{ id: "pages", label: "Pages" },
{ id: "media", label: "Media library" },
],
label: "Content",
},
{
id: "settings",
items: [
{ id: "general", label: "General settings" },
{ id: "users", label: "Users & roles" },
{ id: "plugins", label: "Plugin configuration" },
],
label: "Settings",
},
];
export function Pattern() {
const [value, setValue] = useState<string[]>([]);
const allIds = EXPORT_SECTIONS.flatMap((s) => s.items.map((i) => i.id));
function updateSectionValues(
sectionIds: string[],
newSectionValues: string[],
) {
setValue((prev) => [
...prev.filter((v) => !sectionIds.includes(v)),
...newSectionValues,
]);
}
return (
<div className="w-full max-w-xs space-y-4">
<div>
<p className="font-semibold text-sm">Export data</p>
<p className="mt-0.5 text-muted-foreground text-xs">
Choose what to include in your export.
</p>
</div>
<CheckboxGroup allValues={allIds} onValueChange={setValue} value={value}>
<Label className="font-semibold text-sm">
<Checkbox parent />
Select everything
</Label>
<div className="mt-2 ml-1.5 space-y-4 border-l pl-4">
{EXPORT_SECTIONS.map((section) => {
const sectionIds = section.items.map((i) => i.id);
const sectionValues = value.filter((v) => sectionIds.includes(v));
return (
<div className="space-y-1.5" key={section.id}>
<CheckboxGroup
allValues={sectionIds}
onValueChange={(v) => updateSectionValues(sectionIds, v)}
value={sectionValues}
>
<Label className="font-medium text-sm">
<Checkbox parent />
{section.label}
</Label>
{section.items.map((item) => (
<Label
className="ms-5 text-muted-foreground text-sm"
key={item.id}
>
<Checkbox value={item.id} />
{item.label}
</Label>
))}
</CheckboxGroup>
</div>
);
})}
</div>
</CheckboxGroup>
<p className="text-muted-foreground text-xs">
{value.length} of {allIds.length} items selected
</p>
</div>
);
}
Food Preferences
Two independent CheckboxGroup instances — one for cuisines and one for dietary options — each with a select-all parent and a two-column item grid.
Food preferences
Cuisines
Dietary
"use client";
import { useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { CheckboxGroup } from "@/components/ui/checkbox-group";
import { Label } from "@/components/ui/label";
const CUISINES = [
{ id: "italian", label: "Italian" },
{ id: "japanese", label: "Japanese" },
{ id: "mexican", label: "Mexican" },
{ id: "indian", label: "Indian" },
{ id: "thai", label: "Thai" },
{ id: "american", label: "American" },
];
const DIETARY = [
{ id: "vegetarian", label: "Vegetarian" },
{ id: "vegan", label: "Vegan" },
{ id: "gluten-free", label: "Gluten-free" },
{ id: "halal", label: "Halal" },
];
export function Pattern() {
const [cuisines, setCuisines] = useState<string[]>([]);
const [dietary, setDietary] = useState<string[]>([]);
return (
<div className="w-full max-w-xs space-y-5">
<p className="font-semibold text-sm">Food preferences</p>
<div className="space-y-2">
<p className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
Cuisines
</p>
<CheckboxGroup
allValues={CUISINES.map((c) => c.id)}
onValueChange={setCuisines}
value={cuisines}
>
<Label className="mb-1 text-muted-foreground text-sm">
<Checkbox parent />
All cuisines
</Label>
<div className="ms-5 grid grid-cols-2 gap-x-4 gap-y-1">
{CUISINES.map((c) => (
<Label className="text-sm" key={c.id}>
<Checkbox value={c.id} />
{c.label}
</Label>
))}
</div>
</CheckboxGroup>
</div>
<div className="space-y-2">
<p className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
Dietary
</p>
<CheckboxGroup
allValues={DIETARY.map((d) => d.id)}
onValueChange={setDietary}
value={dietary}
>
<Label className="mb-1 text-muted-foreground text-sm">
<Checkbox parent />
All dietary options
</Label>
<div className="ms-5 grid grid-cols-2 gap-x-4 gap-y-1">
{DIETARY.map((d) => (
<Label className="text-sm" key={d.id}>
<Checkbox value={d.id} />
{d.label}
</Label>
))}
</div>
</CheckboxGroup>
</div>
{(cuisines.length > 0 || dietary.length > 0) && (
<p className="text-muted-foreground text-xs">
{[...cuisines, ...dietary].join(", ")}
</p>
)}
</div>
);
}

