Checkbox Group
Group of related checkboxes backed by an array of selected values. Install Checkbox Group from the VLLNT UI registry with the shadcn CLI.
Group of related checkboxes backed by an array of selected values. Part of the form family in VLLNT UI, it ships as a machine-readable registry entry — copy the source directly into your app with the shadcn CLI and own it, no runtime dependency on a component library.
Preview
Switch between light and dark to inspect the embedded Storybook preview.
Installation
Add Checkbox Group to your project with the shadcn CLI. The source lands in your codebase, ready to adapt:
pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/checkbox-group.jsonSource
"use client";
import * as React from "react";
import { cn } from "../../lib/utils";
import { Checkbox } from "../checkbox/checkbox";
import { Label } from "../label/label";
type CheckboxGroupContextValue = {
disabled: boolean;
name?: string;
toggle: (value: string) => void;
values: string[];
};
const CheckboxGroupContext =
React.createContext<CheckboxGroupContextValue | null>(null);
function useCheckboxGroupContext(): CheckboxGroupContextValue {
const context = React.use(CheckboxGroupContext);
if (!context) {
throw new Error("CheckboxGroupItem must be used within a CheckboxGroup");
}
return context;
}
function useCheckboxGroupValue(
value: string[] | undefined,
defaultValue: string[],
onValueChange?: (value: string[]) => void,
) {
const [internalValue, setInternalValue] = React.useState(defaultValue);
const values = value ?? internalValue;
const toggle = React.useCallback(
(item: string) => {
const nextValues = values.includes(item)
? values.filter((entry) => entry !== item)
: [...values, item];
if (value === undefined) {
setInternalValue(nextValues);
}
onValueChange?.(nextValues);
},
[onValueChange, value, values],
);
return { toggle, values };
}
/** Group of related checkboxes backed by an array of selected values. */
export type CheckboxGroupProps = {
children: React.ReactNode;
className?: string;
defaultValue?: string[];
disabled?: boolean;
name?: string;
onValueChange?: (value: string[]) => void;
orientation?: "horizontal" | "vertical";
value?: string[];
};
const CheckboxGroup = ({
children,
className,
defaultValue = [],
disabled = false,
name,
onValueChange,
orientation = "vertical",
ref,
value,
}: CheckboxGroupProps & { ref?: React.Ref<HTMLDivElement> }) => {
const { toggle, values } = useCheckboxGroupValue(
value,
defaultValue,
onValueChange,
);
const context = React.useMemo<CheckboxGroupContextValue>(
() => ({ disabled, name, toggle, values }),
[disabled, name, toggle, values],
);
return (
<CheckboxGroupContext.Provider value={context}>
<div
className={cn(
"flex gap-3",
orientation === "vertical" ? "flex-col" : "flex-row flex-wrap",
className,
)}
ref={ref}
role="group"
>
{children}
</div>
</CheckboxGroupContext.Provider>
);
};
CheckboxGroup.displayName = "CheckboxGroup";
/** Single checkbox plus label wired into the parent CheckboxGroup. */
export type CheckboxGroupItemProps = {
children?: React.ReactNode;
className?: string;
disabled?: boolean;
id?: string;
value: string;
};
const CheckboxGroupItem = ({
children,
className,
disabled = false,
id,
ref,
value,
}: CheckboxGroupItemProps & { ref?: React.Ref<HTMLDivElement> }) => {
const generatedId = React.useId();
const itemId = id ?? generatedId;
const group = useCheckboxGroupContext();
const checked = group.values.includes(value);
return (
<div className={cn("flex items-center gap-2", className)} ref={ref}>
<Checkbox
checked={checked}
disabled={disabled || group.disabled}
id={itemId}
name={group.name}
onCheckedChange={() => {
group.toggle(value);
}}
value={value}
/>
{children ? <Label htmlFor={itemId}>{children}</Label> : null}
</div>
);
};
CheckboxGroupItem.displayName = "CheckboxGroupItem";
export { CheckboxGroup, CheckboxGroupItem };
Stories
Explore every variant and state in the interactive Storybook:
Preview
Switch between light and dark to inspect the embedded Storybook preview.