Checkbox Group

A group of checkboxes that can be controlled together.

Pick a genre

export default function CheckboxGroupHero() {
  return (
    <div className="flex flex-col gap-3">
      <p className="text-sm font-medium text-foreground">Pick a genre</p>
      <CheckboxGroup defaultValue={['jazz']}>
        {GENRES.map((genre) => (
          <label key={genre.value} className="flex items-center gap-2">
            <Checkbox value={genre.value} />
            <span className="text-sm text-foreground">{genre.label}</span>
          </label>
        ))}
      </CheckboxGroup>
    </div>
  );
}

Installation

pnpm dlx shadcn@latest add "https://ora-ui.com/r/checkbox-group.json"

Usage

import { Checkbox } from '@/registry/ui/checkbox';
import { CheckboxGroup } from '@/registry/ui/checkbox-group';

<CheckboxGroup defaultValue={['jazz']}>
  <label className="flex items-center gap-2">
    <Checkbox value="jazz" />
    <span>Jazz</span>
  </label>
  <label className="flex items-center gap-2">
    <Checkbox value="electronic" />
    <span>Electronic</span>
  </label>
</CheckboxGroup>;

Examples

Default

A labeled group of checkboxes with a default selection.

Sound preferences

export default function CheckboxGroupDefault() {
  return (
    <div className="flex flex-col gap-3">
      <p className="text-sm font-medium text-foreground">Sound preferences</p>
      <CheckboxGroup defaultValue={['acoustic']}>
        {PREFERENCES.map((pref) => (
          <label key={pref.value} className="flex items-center gap-2">
            <Checkbox value={pref.value} />
            <span className="text-sm text-foreground">{pref.label}</span>
          </label>
        ))}
      </CheckboxGroup>
    </div>
  );
}

Variant

Two visual styles are available: solid fills the checkbox on check, surface uses a border treatment. Set variant on CheckboxGroup to apply it to all checkboxes in the group.

Pick a genre

export function CheckboxGroupSolid() {
  return (
    <div className="flex flex-col gap-3">
      <p className="text-sm font-medium text-foreground">Pick a genre</p>
      <CheckboxGroup variant="solid" defaultValue={['jazz']}>
        {GENRES.map((genre) => (
          <label key={genre.value} className="flex items-center gap-2">
            <Checkbox value={genre.value} />
            <span className="text-sm text-foreground">{genre.label}</span>
          </label>
        ))}
      </CheckboxGroup>
    </div>
  );
}

Theme

Use the theme prop to apply a color theme to the group. gray is the default; accent uses the brand color.

Pick a genre

export function CheckboxGroupGray() {
  return (
    <div className="flex flex-col gap-3">
      <p className="text-sm font-medium text-foreground">Pick a genre</p>
      <CheckboxGroup theme="gray" defaultValue={['jazz']}>
        {GENRES.map((genre) => (
          <label key={genre.value} className="flex items-center gap-2">
            <Checkbox value={genre.value} />
            <span className="text-sm text-foreground">{genre.label}</span>
          </label>
        ))}
      </CheckboxGroup>
    </div>
  );
}

Parent checkbox

Use a parent Checkbox with the parent prop alongside allValues to create a "select all" control that reflects indeterminate state when only some items are checked.

export default function CheckboxGroupParent() {
  const [values, setValues] = React.useState<string[]>(['jazz']);

  const allChecked = values.length === GENRES.length;
  const someChecked = values.length > 0 && !allChecked;

  return (
    <CheckboxGroup
      value={values}
      onValueChange={setValues}
      allValues={GENRES.map((g) => g.value)}
      className="gap-3"
    >
      <label className="flex items-center gap-2">
        <Checkbox
          parent
          checked={allChecked}
          indeterminate={someChecked}
          onCheckedChange={() => setValues(allChecked ? [] : GENRES.map((g) => g.value))}
        />
        <span className="text-sm font-medium text-foreground">All genres</span>
      </label>
      <div className="ml-6 flex flex-col gap-2 border-l border-line-subtle pl-4">
        {GENRES.map((genre) => (
          <label key={genre.value} className="flex items-center gap-2">
            <Checkbox value={genre.value} />
            <span className="text-sm text-foreground">{genre.label}</span>
          </label>
        ))}
      </div>
    </CheckboxGroup>
  );
}

Nested parent checkbox

Combine a top-level parent with nested category parents to build multi-level selection trees.

export default function CheckboxGroupNested() {
  const [values, setValues] = React.useState<string[]>(['house', 'pop']);

  const allChecked = values.length === ALL_VALUES.length;
  const someChecked = values.length > 0 && !allChecked;

  const allElectronicChecked = ELECTRONIC.every((i) => values.includes(i.value));
  const someElectronicChecked =
    ELECTRONIC.some((i) => values.includes(i.value)) && !allElectronicChecked;

  return (
    <CheckboxGroup
      value={values}
      onValueChange={setValues}
      allValues={ALL_VALUES}
      className="gap-3"
    >
      <label className="flex items-center gap-2">
        <Checkbox
          parent
          checked={allChecked}
          indeterminate={someChecked}
          onCheckedChange={() => setValues(allChecked ? [] : ALL_VALUES)}
        />
        <span className="text-sm font-medium text-foreground">All genres</span>
      </label>
      <div className="ml-6 flex flex-col gap-2 border-l border-line-subtle pl-4">
        <label className="flex items-center gap-2">
          <Checkbox value="pop" />
          <span className="text-sm text-foreground">Pop</span>
        </label>
        <label className="flex items-center gap-2">
          <Checkbox value="jazz" />
          <span className="text-sm text-foreground">Jazz</span>
        </label>
        <div className="flex flex-col gap-2">
          <label className="flex items-center gap-2">
            <Checkbox
              checked={allElectronicChecked}
              indeterminate={someElectronicChecked}
              onCheckedChange={() =>
                setValues((prev) =>
                  allElectronicChecked
                    ? prev.filter((v) => !ELECTRONIC.map((i) => i.value).includes(v))
                    : [...new Set([...prev, ...ELECTRONIC.map((i) => i.value)])]
                )
              }
            />
            <span className="text-sm font-medium text-foreground">Electronic</span>
          </label>
          <div className="ml-6 flex flex-col gap-2 border-l border-line-subtle pl-4">
            {ELECTRONIC.map((item) => (
              <label key={item.value} className="flex items-center gap-2">
                <Checkbox value={item.value} />
                <span className="text-sm text-foreground">{item.label}</span>
              </label>
            ))}
          </div>
        </div>
      </div>
    </CheckboxGroup>
  );
}

API Reference

CheckboxGroup

PropTypeDefault
variant"solid" | "surface""solid"
theme"gray" | "accent""gray"

All other props are forwarded to the underlying Base UI CheckboxGroup primitive.