Skip to content

Checkbox Group

Shared state for a series of Checkboxes. Renders a <div role="group"> and owns the list of ticked values, so the answer to a multiple-choice question is one array instead of one boolean per option.

checkbox-group/basic.tsx
Send the weekly digest on

Unlike a RadioGroup or a ToggleGroup, this is not a keyboard mode. Every checkbox in the group stays its own tab stop, exactly as a run of native checkboxes would — grouping here is about state and about naming, and the arrow keys are left to the page.

Import

import { Checkbox, CheckboxGroup } from "@forte-ui/react";

Give each Checkbox a value; the group's value is the list of ticked ones. A checkbox without a value can never appear in it, and stays a plain standalone checkbox holding its own state.

Examples

Labelling

The group has no name of its own, and it cannot be given one with a <label> — a <label> resolves to a single control, and role="group" is not a labelable element. Point aria-labelledby at a heading you render yourself, and name each checkbox with an enclosing <label>.

checkbox-group/labelling.tsx
Allowed network protocols

The enclosing label works because Checkbox renders a <span> with a hidden <input> beside it, and the input is what the label resolves to. Inside a Field.Root — the pattern the rest of this page uses — a Field.Label nativeLabel={false} does the heading half for you, and a Field.Item per row does the other half.

Rendering as a native button

Pass nativeButton render={<button />} when you want a real <button> instead of the default <span>. That also moves id from the hidden input onto the root, which is what makes a sibling <label htmlFor> resolve. For a button inside an enclosing label, use the render callback instead.

checkbox-group/native-button.tsx
Allowed network protocols

Orientation

orientation="horizontal" lays the checkboxes out in a row instead of a column, and wraps when they run out of room.

checkbox-group/orientation.tsx
Export as

Controlled

Pass value and onValueChange for a controlled group, defaultValue for an uncontrolled one. The callback receives the new array first and Base UI's event details second.

checkbox-group/controlled.tsx
Email me about

2 of 4 selected.

Holding the array yourself is also what lets something outside the group write to it — a "select all" button, a URL parameter, a saved preset.

Parent checkbox

Add parent to a checkbox and the group drives it: ticked when every child is ticked, mixed when only some are, unticked when none are. The mixed state draws a dash instead of a tick and reports aria-checked="mixed".

checkbox-group/parent.tsx

Three details worth knowing about a parent checkbox:

  • Activating it while it is mixed ticks every child. Activating it again unticks them all. A third activation restores the partial selection it started from.
  • It is left out of form submission: Base UI drops the name on a parent checkbox, so only the children submit values.
  • Its aria-controls is built from ids the group generates for the children, so do not pass your own id to a child checkbox inside a parent group — it breaks the association.

Nested parent checkboxes

A group can contain another group, each with its own parent. Nothing connects the two automatically, though: a row that is both a value in the outer group and the parent of the inner one is two facts about two different groups, and you keep them in step yourself.

checkbox-group/nested.tsx

Two pieces of that are easy to miss. The outer parent cannot see the level below, so a partly-filled inner group has to be reported upwards with an explicit indeterminate — otherwise the top checkbox reads as empty while four sub-permissions are granted. And unticking the middle row should only clear the inner group when it was completely full, since that is the state the row was standing for; clearing a partial selection throws away choices the user made one level down.

Form integration

Inside a Form, a Field.Root carries the name the array is submitted under. Render the fieldset as the group so that one element is both the <fieldset> Base UI names with its legend and the group that owns the value.

checkbox-group/form.tsx
Allowed network protocols

One thing that composition costs you: Fieldset.Root normally renders a real <fieldset>, and disabled on it works the native way — it reaches controls the library has never heard of. Rendered as the group it is a <div>, so put disabled on the CheckboxGroup or the Field.Root instead.

Disabled

disabled on the group disables every checkbox in it. Put it on the surrounding Field.Root instead and the labels dim with the boxes, which is what keeps the reason an option is unavailable from sitting at full contrast beside a greyed-out control. One row at a time is disabled on that row's Field.Item, for exactly the same reason.

checkbox-group/disabled.tsx
Replicate to

Replication is fixed for the duration of a migration.

Replicate to

Accessibility

Keyboard interactions
KeyBehaviour
TabMoves focus to the next checkbox. Every checkbox in the group is its own tab stop — the group is not a roving-focus widget.
SpaceToggles the focused checkbox. On a parent checkbox it drives the whole group — all, none, then back to the earlier partial selection.
EnterDoes not toggle. Inside a form it submits the form, matching a native checkbox.

Each checkbox still needs its own name too — an enclosing <label>, a Field.Item with a Field.Label, or a sibling <label htmlFor> paired with nativeButton. See Checkbox for the rest: the mark, the mixed state, forced colours and motion all belong to the individual control.

Theming

The group declares one property of its own; the checkboxes inside are themed through the --forte-checkbox-* properties documented on Checkbox.

Theming tokens for Checkbox
PropertyControlsDefault
--forte-checkbox-group-gapGap between items in the group. var(--forte-space-4) when orientation="horizontal".var(--forte-space-2)

API reference

CheckboxGroup

Props for CheckboxGroup
PropTypeDefaultDescription
allValuesstring[]undefinedValues of *all* checkboxes in the group, ticked or not. Needed only for a parent checkbox: it is how the group knows the difference between "some" and "all" and therefore when the parent is indeterminate.
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.
defaultValuestring[]undefinedValues of the checkboxes that start ticked when uncontrolled.
disabledbooleanfalseDisables every checkbox in the group.
orientationCheckboxGroupOrientationverticalDirection the checkboxes are laid out in. Horizontal groups wrap.
valuestring[]undefinedValues of the checkboxes that are ticked. Pairs with `onValueChange` for a controlled group; use `defaultValue` for an uncontrolled one. Required if any child uses the `parent` prop.

onValueChange, render and the rest of Base UI's CheckboxGroup props pass through unchanged.

Checkbox

See Checkbox for the full prop table.