Checkbox Group

Developer Experience (DX)

How developers use the Checkbox Group, with one consistent, typed API that's built from Checkboxes and works with your forms however you build them.

Next, let's look at how developers use the Checkbox Group. It has one API, with the same names as the Checkbox and every other component, it's typed end to end, and it works with your forms however you build them.

import { useState } from "react";
import { CheckboxGroup } from "@/components/CheckboxGroup";

const emails = [
  { value: "news", label: "Product news" },
  { value: "tips", label: "Tips and guides" },
  { value: "events", label: "Events near you" },
];

export function Emails() {
  const [picked, setPicked] = useState<string[]>([]);

  return (
    <CheckboxGroup name="emails" options={emails} value={picked} onValueChange={setPicked}>
      Which emails would you like?
    </CheckboxGroup>
  );
}

Consistent

Once you've learned the Checkbox's API, you already know most of the Checkbox Group's. These names mean the same thing everywhere in Open Components:

NameKindWhat it means
sizePropxs, sm, md, lg or xl, with the same text everywhere
disabledPropThe component can't be used, and here, nothing inside it either
namePropThe name its form sends it under, which every one of its boxes shares
valuePropThe checked options' values, which you bind the way your framework binds a form control's
optionsPropWhat people can pick from, each with a value and a label
orientationPropvertical or horizontal, the way its options run
selectAllPropThe label of a box that checks or clears every option
Default slotSlotThe legend, which names it, as the label names a Checkbox

We recommend size="md" and orientation="vertical" as the defaults. The Checkbox Group also leaves a few things out, on purpose:

  • There are no Checkboxes between its tags. The options are a prop, so the group can render a Checkbox for each one, give them all its name, bind each one to its value and work out the select-all box for you, and a list of options from your data drops straight in.
  • There's no required. HTML has no way to require one of a group of checkboxes, so the group leaves it to your code, as under Pick at least one.
  • There's no hint or error. Each of them is an element of its own, linked by ID, as on the Checkbox and the Input, so the group fits any layout.
  • There's no color or variant, since its boxes have none either.

Type-safe

Props, events and slots are typed, so your editor can autocomplete them and your type checker (tsc, vue-tsc, svelte-check, ngc or astro check) catches mistakes like orientation="row", which isn't one of the orientations, or a value that isn't a list.

PropTypeDefaultDescription
optionsCheckboxGroupOption[]One Checkbox for each, in this order
namestringThe name every box shares, which its form sends
valuestring[][]The checked options' values, when you bind it. Without it, the group keeps its own
size"xs" | "sm" | "md" | "lg" | "xl""md"How big its boxes, their labels and its legend are
orientation"vertical" | "horizontal""vertical"Whether its options run in a column or in a row
disabledbooleanfalseDisables every box in it, with <fieldset disabled>
selectAllstringThe label of a select-all box above the options. Without it, there's none
Option fieldTypeDescription
valuestringWhat the form sends while it's checked, and what the value lists
labelstringThe text that names its Checkbox
EventPayloadDescription
changeEventFires when people check or uncheck any of its boxes, select-all box included

It comes from the box that changed, and bubbles up to the <fieldset>, so one listener hears every box, and event.target is the one that changed. A binding to value follows the group for you, with the new list of values.

SlotDescription
DefaultThe legend, which names the group

We also export CheckboxGroupProps and CheckboxGroupOption, which come in handy for wrappers and options from your data:

import type { CheckboxGroupOption } from "@/components/CheckboxGroup";

const emails: CheckboxGroupOption[] = topics.map((topic) => ({ value: topic.id, label: topic.title }));

Composable

The Checkbox Group renders a <fieldset>, and everything you pass it that isn't a prop lands there, including id, class, ARIA attributes and event listeners. A ref to the Checkbox Group reaches the <fieldset> too, which is what you need to focus its first box when a submit fails.

Forms

Every box shares the group's name, and each checked box sends it with its own value, so the form sends one field with a value for each checked option, in the options' order, and nothing at all while none is checked. Read it with getAll(), since get() only returns the first value. That also means the form works before your JavaScript loads.

import type { FormEvent } from "react";

export function Preferences() {
  function save(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    // One name, with a value for each checked option, so read them all.
    api.savePreferences({ emails: data.getAll("emails") });
  }

  return (
    <form onSubmit={save}>
      <CheckboxGroup name="emails" options={emails} defaultValue={["news"]}>Which emails would you like?</CheckboxGroup>
      <Button type="submit" color="primary">Save preferences</Button>
    </form>
  );
}

Keep in mind that a server reading the form sees emails=news&emails=tips, the same name once for each checked option, and no field at all when none is checked. Some frameworks keep only the last value of a repeated name unless you ask for all of them, like PHP, which needs the name to end in [], so check how yours reads it.

With other components

  • Each option is a Checkbox, which sets its own variables on itself. To restyle every box in a group, set them from a class on the group with a descendant selector, like .brand .checkbox { --checkbox--box--fill: … }, that gets past your framework's style scoping, as under Tokens. A value on the group itself doesn't reach them.
  • The Field, on our Roadmap, will render a group's hint and error message, and set its aria-describedby and aria-invalid, as it will for the Checkbox and the Input.
  • A Button that submits the form goes after the group, and takes the same size, so their text lines up.

Controllable

The Checkbox Group doesn't keep any state you can't reach. Its value is yours to bind, or to leave to the group, and every other state is a prop or an attribute that you control:

Which emails would you like?

You picked: news

How can we reach you?
const [picked, setPicked] = useState(["news"]);

<CheckboxGroup name="emails" options={emails} value={picked} onValueChange={setPicked}>Which emails would you like?</CheckboxGroup>
<p>You picked: {picked.join(", ") || "nothing yet"}</p>

<CheckboxGroup name="contact" options={contact} defaultValue={["email"]}>How can we reach you?</CheckboxGroup>
StateHow you control it
Valuevalue, bound to your state, or left to the group
Select allselectAll, with its checked and mixed states worked out from the value
Disableddisabled, or a <fieldset disabled> around it
Invalidaria-invalid, set while its error message shows (see Pick at least one)
FocusA ref to its <fieldset>, and focus() on its first box

When you bind value, the boxes always show the list you hold. If a change doesn't make it into your state, like an option people aren't allowed to drop, the box goes back as soon as it renders, and so does the select-all box. When you don't bind it, the group keeps its own value, and starts out from the one you give it.

DX Checklist

RuleLevelScopeRequirementBasisCheck
checkbox-group/shared-vocabularyMustComponentIt uses the shared names: size, disabled, name, value, options, orientation, selectAll, the default slot for its legend and changeOpen ComponentsReview
checkbox-group/typedMustComponentProps, events and slots are typed, with unions rather than string, and the value is a list of stringsOpen ComponentsType check
checkbox-group/composes-checkboxMustComponentEach option and the select-all box is a Checkbox, so every Checkbox rule holds for each of themOpen ComponentsReview
checkbox-group/form-controlMustComponentEvery option shares one name, and its form sends a value for each checked option, in the options' order, and nothing for the select-all boxHTMLUnit test
checkbox-group/controllableShouldComponentvalue is a list that can be bound or left to the group, and a bound group always shows the value you holdOpen ComponentsUnit test
checkbox-group/fieldset-attributesMustComponentAttributes, listeners and refs land on the <fieldset>, and each box's change bubbles up to itOpen ComponentsUnit test

FAQ

Copyright © 2026