f-ui
Components

Checkbox Group

Visible short-list multi choice with vertical or horizontal layout.

Checkbox Group shows a short list of options as always-visible checkboxes so users can turn several on at once. Pass the shared Option shape from Select (value, label, optional disable). Prefer it when every choice should stay on screen; reach for Multi-Select when the list is long, searchable, or async; use a single Checkbox for a boolean or consent field.

When To Use

  • Pick several values from a short, always-visible list (notification channels, features, tags).
  • Prefer Checkbox Group for ≤ ~7 independent options so users can scan without opening a popover.
  • Use Multi-Select when the list is long, searchable, or async.
  • Use Radio Group when the choice is mutually exclusive.
  • Use a single Checkbox for a boolean or consent field (terms, marketing opt-in).

Interactions

EventBehavior
Check an enabled optionCalls onValueChange with that option’s value appended (SelectValue[]).
Uncheck an enabled optionCalls onValueChange with that value removed — including when the last option is cleared.
orientation="horizontal"Options lay out in a wrapping row instead of a vertical stack.
Option with disable: trueThat checkbox stays non-interactive; siblings remain selectable.
Group disabledEvery option is non-interactive.

Empty Value & Clearing

Unlike Radio Group, clearing is a first-class user action:

SituationBehavior
Empty selectionAlways [] — never null. Omit value or pass [] for none checked.
Uncheck the last optionEmits []. Valid product state for “optional multi” fields.
Required “at least one”Use form required (Formily treats [] as empty) or a custom validator.
vs Radio GroupRadios cannot uncheck to empty; Checkbox Group can.

Do not model the field as null for empty — keep the type SelectValue[] end to end.

Installing

pnpm dlx shadcn@latest add https://ui.isaacfei.com/r/checkbox-group.json
npx shadcn@latest add https://ui.isaacfei.com/r/checkbox-group.json
yarn dlx shadcn@latest add https://ui.isaacfei.com/r/checkbox-group.json
bun x shadcn@latest add https://ui.isaacfei.com/r/checkbox-group.json

Or with a namespace: npx shadcn@latest add @f-ui/checkbox-group.

The CLI pulls checkbox and label from the default shadcn registry, and select from f-ui for the shared Option / SelectValue types.

Usage

import { CheckboxGroup } from '@/components/f-ui/checkbox-group/checkbox-group';

const options = [
  { label: 'Email', value: 'email' },
  { label: 'SMS', value: 'sms' },
  { label: 'Push', value: 'push' },
];

<CheckboxGroup
  aria-label="Notification channels"
  options={options}
  value={value}
  onValueChange={setValue}
/>

Examples

Notification Channels

Controlled vertical group for a small multi-select enum. Toggle options and watch the readout update.

Selected: email

"use client";

import { useState } from "react";

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

const options = [
  { label: "Email", value: "email" },
  { label: "SMS", value: "sms" },
  { label: "Push", value: "push" },
];

export function CheckboxGroupDemo() {
  const [value, setValue] = useState<(string | number)[]>(["email"]);
  return (
    <div className="space-y-3">
      <CheckboxGroup
        aria-label="Notification channels"
        options={options}
        value={value}
        onValueChange={setValue}
      />
      <p className="text-muted-foreground text-xs">
        Selected:{" "}
        <span className="text-foreground font-medium">
          {value.length ? value.join(", ") : "none"}
        </span>
      </p>
    </div>
  );
}

Horizontal Size

Use orientation="horizontal" for compact multi-choice that reads left-to-right (sizes, ratings, short codes).

"use client";

import { useState } from "react";

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

export function CheckboxGroupHorizontalDemo() {
  const [value, setValue] = useState<(string | number)[]>(["s", "m"]);
  return (
    <CheckboxGroup
      aria-label="Size"
      orientation="horizontal"
      options={[
        { label: "S", value: "s" },
        { label: "M", value: "m" },
        { label: "L", value: "l" },
      ]}
      value={value}
      onValueChange={setValue}
    />
  );
}

Empty Selection

Uncheck every option — the value becomes [], shown in the readout. That is the group’s empty state (not null).

Selected: email

"use client";

import { useState } from "react";

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

const options = [
  { label: "Email", value: "email" },
  { label: "SMS", value: "sms" },
  { label: "Push", value: "push" },
];

/**
 * Empty selection is always `[]` (never `null`). Unchecking the last option
 * is a first-class user action — unlike Radio Group.
 */
export function CheckboxGroupEmptyDemo() {
  const [value, setValue] = useState<(string | number)[]>(["email"]);

  return (
    <div className="space-y-3">
      <CheckboxGroup
        aria-label="Notification channels"
        options={options}
        value={value}
        onValueChange={setValue}
      />
      <p className="text-muted-foreground text-xs">
        Selected:{" "}
        <span className="text-foreground font-medium">
          {value.length ? value.join(", ") : "[] (empty)"}
        </span>
      </p>
    </div>
  );
}

Disabled Options and Group

Per-option disable blocks one choice; group disabled locks the whole set.

"use client";

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

export function CheckboxGroupDisabledDemo() {
  return (
    <div className="space-y-6">
      <CheckboxGroup
        aria-label="Plan with disabled option"
        options={[
          { label: "Free", value: "free" },
          { label: "Pro", value: "pro" },
          { label: "Enterprise", value: "ent", disable: true },
        ]}
        value={["free"]}
      />
      <CheckboxGroup
        aria-label="Disabled group"
        disabled
        options={[
          { label: "A", value: "a" },
          { label: "B", value: "b" },
        ]}
        value={["a"]}
      />
    </div>
  );
}

Edge Cases & Semantics

QuestionAnswer
Empty value type?[] only — not null.
Can the user clear all?Yes — uncheck every option.
Selection order?Values append in click order; uncheck removes that entry. Do not assume option-list order.
Value not in options (stale)?Stale entries stay in value until you filter them — the group only toggles known options.
Indeterminate / “select all” parent?Not built in. Compose separately if you need bulk select.
string vs number option values?Supported; equality uses the original Option.value.
Standalone (no FormItem label)?Pass aria-label on the group.
Formily kind name?kind="checkboxGroup" + componentProps.options. See Form (Formily) — Choice Groups.
vs single Checkbox?Single Checkbox is boolean / consent — not a multi-value list.
vs Multi-Select / kind="multiSelect"?Multi-Select for long / searchable / async / creatable lists with chips.

Composition

CheckboxGroup
└── (per option) item row
    ├── Checkbox
    └── Label

API Reference

Props

PropTypeDefault
valueSelectValue[]
onValueChange(value: SelectValue[]) => void
optionsreadonly Option[]
orientation'vertical' | 'horizontal''vertical'
disabledbooleanfalse
idstringauto
classNamestring
classNamesPartial<Record<CheckboxGroupSlot, string>>
aria-labelstring
aria-invalidboolean
aria-requiredboolean
aria-describedbystring

Option is { value: SelectValue; label: string; disable?: boolean } from Select. SelectValue is string | number.

When value is omitted, the group treats selection as [].

Slots

SlotApplied to
rootOuter checkbox group container
itemEach option row (Checkbox + Label)
labelOption Label

On this page