Skip to contents

Checkbox Group

Provides shared state to a series of checkboxes.

Apples
import { createUniqueId } from 'solid-js';
import type { JSX } from '@solidjs/web';
import { Checkbox } from 'base-ui-solid/checkbox';
import { CheckboxGroup } from 'base-ui-solid/checkbox-group';
import styles from './index.module.css';

export default function ExampleCheckboxGroup() {
  const id = createUniqueId();
  return (
    <CheckboxGroup aria-labelledby={id} defaultValue={['fuji-apple']} class={styles.CheckboxGroup}>
      <div class={styles.Caption} id={id}>
        Apples
      </div>

      <label class={styles.Item}>
        <Checkbox.Root name="apple" value="fuji-apple" class={styles.Checkbox}>
          <Checkbox.Indicator class={styles.Indicator}>
            <CheckIcon />
          </Checkbox.Indicator>
        </Checkbox.Root>
        Fuji
      </label>

      <label class={styles.Item}>
        <Checkbox.Root name="apple" value="gala-apple" class={styles.Checkbox}>
          <Checkbox.Indicator class={styles.Indicator}>
            <CheckIcon />
          </Checkbox.Indicator>
        </Checkbox.Root>
        Gala
      </label>

      <label class={styles.Item}>
        <Checkbox.Root name="apple" value="granny-smith-apple" class={styles.Checkbox}>
          <Checkbox.Indicator class={styles.Indicator}>
            <CheckIcon />
          </Checkbox.Indicator>
        </Checkbox.Root>
        Granny Smith
      </label>
    </CheckboxGroup>
  );
}

function CheckIcon(props: JSX.IntrinsicElements['svg']) {
  return (
    <svg
      width="16"
      height="16"
      viewBox="0 0 16 16"
      fill="none"
      stroke="currentColor"
      {...props}
      style={
        typeof props.style === 'string'
          ? `display: block; ${props.style}`
          : { display: 'block', ...(typeof props.style === 'object' ? props.style : {}) }
      }
    >
      <path d="m2.5 8.5 4 4 7-9" />
    </svg>
  );
}

Usage guidelines

Anatomy

Checkbox Group is composed together with Checkbox. Import the components and place them together:

Anatomy
import { Checkbox } from 'base-ui-solid/checkbox';
import { CheckboxGroup } from 'base-ui-solid/checkbox-group';

<CheckboxGroup>
  <Checkbox.Root />
</CheckboxGroup>;

Examples

Labeling a checkbox group

Label the group with aria-labelledby and a sibling label element:

Using aria-labelledby to label a checkbox group
<div id="protocols-label">Allowed network protocols</div>
<CheckboxGroup aria-labelledby="protocols-label">{/* ... */}</CheckboxGroup>

An enclosing <label> is the simplest labeling pattern for each checkbox:

Using an enclosing label to label a checkbox
<label>
  <Checkbox.Root value="http" />
  HTTP
</label>

Rendering as a native button

By default, <Checkbox.Root> renders a <span> element to support enclosing labels. Prefer rendering each checkbox as a native button when using sibling labels (for/id).

Sibling label pattern with a native button
<div id="protocols-label">Allowed network protocols</div>
<CheckboxGroup aria-labelledby="protocols-label">
  <div>
    <label for="protocol-http">HTTP</label>
    <Checkbox.Root id="protocol-http" value="http" nativeButton render="button">
      <Checkbox.Indicator />
    </Checkbox.Root>
  </div>
</CheckboxGroup>

Native buttons with wrapping labels are supported by using the render callback to avoid invalid HTML, so the hidden input is placed outside the label:

Render callback
<div id="protocols-label">Allowed network protocols</div>
<CheckboxGroup aria-labelledby="protocols-label">
  <Checkbox.Root
    value="http"
    nativeButton
    render={(buttonProps) => (
      <label>
        <button {...buttonProps} />
        HTTP
      </label>
    )}
  />
</CheckboxGroup>

Form integration

Use Field and Fieldset for group labeling and form integration:

Using Checkbox Group in a form
<Form>
  <Field.Root name="allowedNetworkProtocols">
    <Fieldset.Root render="CheckboxGroup">
      <Fieldset.Legend>Allowed network protocols</Fieldset.Legend>
      <Field.Item>
        <Field.Label>
          <Checkbox.Root value="http" />
          HTTP
        </Field.Label>
      </Field.Item>
      <Field.Item>
        <Field.Label>
          <Checkbox.Root value="https" />
          HTTPS
        </Field.Label>
      </Field.Item>
      <Field.Item>
        <Field.Label>
          <Checkbox.Root value="ssh" />
          SSH
        </Field.Label>
      </Field.Item>
    </Fieldset.Root>
  </Field.Root>
</Form>

Parent checkbox

A checkbox that controls other checkboxes within a <CheckboxGroup> can be created:

  1. Make <CheckboxGroup> a controlled component
  2. Pass an array of all the child checkbox values to the allValues prop on the <CheckboxGroup> component
  3. Add the parent boolean prop to the parent <Checkbox.Root>

The group controls the parent checkbox’s indeterminate state when some, but not all, child checkboxes are checked.

import { createSignal, createUniqueId } from 'solid-js';
import type { JSX } from '@solidjs/web';
import { Checkbox } from 'base-ui-solid/checkbox';
import { CheckboxGroup } from 'base-ui-solid/checkbox-group';
import styles from './index.module.css';

const fruits = ['fuji-apple', 'gala-apple', 'granny-smith-apple'];

export default function ExampleCheckboxGroup() {
  const id = createUniqueId();
  const [value, setValue] = createSignal<string[]>([]);

  return (
    <CheckboxGroup
      aria-labelledby={id}
      value={value()}
      onValueChange={setValue}
      allValues={fruits}
      class={styles.CheckboxGroup}
      style={{ 'margin-left': '1rem' }}
    >
      <label class={styles.Item} id={id} style={{ 'margin-left': '-1rem' }}>
        <Checkbox.Root class={styles.Checkbox} parent>
          <Checkbox.Indicator
            class={styles.Indicator}
            render={(props, state) => (
              <span {...props}>{state.indeterminate ? <HorizontalRuleIcon /> : <CheckIcon />}</span>
            )}
          />
        </Checkbox.Root>
        Apples
      </label>

      <label class={styles.Item}>
        <Checkbox.Root value="fuji-apple" class={styles.Checkbox}>
          <Checkbox.Indicator class={styles.Indicator}>
            <CheckIcon />
          </Checkbox.Indicator>
        </Checkbox.Root>
        Fuji
      </label>

      <label class={styles.Item}>
        <Checkbox.Root value="gala-apple" class={styles.Checkbox}>
          <Checkbox.Indicator class={styles.Indicator}>
            <CheckIcon />
          </Checkbox.Indicator>
        </Checkbox.Root>
        Gala
      </label>

      <label class={styles.Item}>
        <Checkbox.Root value="granny-smith-apple" class={styles.Checkbox}>
          <Checkbox.Indicator class={styles.Indicator}>
            <CheckIcon />
          </Checkbox.Indicator>
        </Checkbox.Root>
        Granny Smith
      </label>
    </CheckboxGroup>
  );
}

function CheckIcon(props: JSX.IntrinsicElements['svg']) {
  return (
    <svg
      width="16"
      height="16"
      viewBox="0 0 16 16"
      fill="none"
      stroke="currentColor"
      {...props}
      style={
        typeof props.style === 'string'
          ? `display: block; ${props.style}`
          : { display: 'block', ...(typeof props.style === 'object' ? props.style : {}) }
      }
    >
      <path d="m2.5 8.5 4 4 7-9" />
    </svg>
  );
}

function HorizontalRuleIcon(props: JSX.IntrinsicElements['svg']) {
  return (
    <svg
      width="12"
      height="12"
      viewBox="0 0 24 24"
      fill="currentColor"
      stroke-width={1}
      {...props}
      style={
        typeof props.style === 'string'
          ? `display: block; ${props.style}`
          : { display: 'block', ...(typeof props.style === 'object' ? props.style : {}) }
      }
    >
      <line
        x1="3"
        y1="12"
        x2="21"
        y2="12"
        stroke="currentColor"
        vector-effect="non-scaling-stroke"
      />
    </svg>
  );
}

Nested parent checkbox

import { createSignal, createUniqueId } from 'solid-js';
import type { JSX } from '@solidjs/web';
import { Checkbox } from 'base-ui-solid/checkbox';
import { CheckboxGroup } from 'base-ui-solid/checkbox-group';
import styles from './index.module.css';

const mainPermissions = ['view-dashboard', 'manage-users', 'access-reports'];
const userManagementPermissions = ['create-user', 'edit-user', 'delete-user', 'assign-roles'];

export default function PermissionsForm() {
  const id = createUniqueId();
  const [mainValue, setMainValue] = createSignal<string[]>([]);
  const [managementValue, setManagementValue] = createSignal<string[]>([]);

  return (
    <CheckboxGroup
      aria-labelledby={id}
      value={mainValue()}
      onValueChange={(value) => {
        if (value.includes('manage-users')) {
          setManagementValue(userManagementPermissions);
        } else if (managementValue().length === userManagementPermissions.length) {
          setManagementValue([]);
        }
        setMainValue(value);
      }}
      allValues={mainPermissions}
      class={styles.CheckboxGroup}
      style={{ 'margin-left': '1rem' }}
    >
      <label class={styles.Item} id={id} style={{ 'margin-left': '-1rem' }}>
        <Checkbox.Root
          class={styles.Checkbox}
          parent
          indeterminate={
            managementValue().length > 0 &&
            managementValue().length !== userManagementPermissions.length
          }
        >
          <Checkbox.Indicator
            class={styles.Indicator}
            render={(props, state) => (
              <span {...props}>{state.indeterminate ? <HorizontalRuleIcon /> : <CheckIcon />}</span>
            )}
          />
        </Checkbox.Root>
        User Permissions
      </label>

      <label class={styles.Item}>
        <Checkbox.Root value="view-dashboard" class={styles.Checkbox}>
          <Checkbox.Indicator class={styles.Indicator}>
            <CheckIcon />
          </Checkbox.Indicator>
        </Checkbox.Root>
        View Dashboard
      </label>

      <label class={styles.Item}>
        <Checkbox.Root value="access-reports" class={styles.Checkbox}>
          <Checkbox.Indicator class={styles.Indicator}>
            <CheckIcon />
          </Checkbox.Indicator>
        </Checkbox.Root>
        Access Reports
      </label>

      <CheckboxGroup
        aria-labelledby="manage-users-caption"
        class={styles.CheckboxGroup}
        value={managementValue()}
        onValueChange={(value) => {
          if (value.length === userManagementPermissions.length) {
            setMainValue((prev) => Array.from(new Set([...prev, 'manage-users'])));
          } else {
            setMainValue((prev) => prev.filter((v) => v !== 'manage-users'));
          }
          setManagementValue(value);
        }}
        allValues={userManagementPermissions}
        style={{ 'margin-left': '1rem' }}
      >
        <label class={styles.Item} id="manage-users-caption" style={{ 'margin-left': '-1rem' }}>
          <Checkbox.Root class={styles.Checkbox} parent>
            <Checkbox.Indicator
              class={styles.Indicator}
              render={(props, state) => (
                <span {...props}>
                  {state.indeterminate ? <HorizontalRuleIcon /> : <CheckIcon />}
                </span>
              )}
            />
          </Checkbox.Root>
          Manage Users
        </label>

        <label class={styles.Item}>
          <Checkbox.Root value="create-user" class={styles.Checkbox}>
            <Checkbox.Indicator class={styles.Indicator}>
              <CheckIcon />
            </Checkbox.Indicator>
          </Checkbox.Root>
          Create User
        </label>

        <label class={styles.Item}>
          <Checkbox.Root value="edit-user" class={styles.Checkbox}>
            <Checkbox.Indicator class={styles.Indicator}>
              <CheckIcon />
            </Checkbox.Indicator>
          </Checkbox.Root>
          Edit User
        </label>

        <label class={styles.Item}>
          <Checkbox.Root value="delete-user" class={styles.Checkbox}>
            <Checkbox.Indicator class={styles.Indicator}>
              <CheckIcon />
            </Checkbox.Indicator>
          </Checkbox.Root>
          Delete User
        </label>

        <label class={styles.Item}>
          <Checkbox.Root value="assign-roles" class={styles.Checkbox}>
            <Checkbox.Indicator class={styles.Indicator}>
              <CheckIcon />
            </Checkbox.Indicator>
          </Checkbox.Root>
          Assign Roles
        </label>
      </CheckboxGroup>
    </CheckboxGroup>
  );
}

function CheckIcon(props: JSX.IntrinsicElements['svg']) {
  return (
    <svg
      width="16"
      height="16"
      viewBox="0 0 16 16"
      fill="none"
      stroke="currentColor"
      {...props}
      style={
        typeof props.style === 'string'
          ? `display: block; ${props.style}`
          : { display: 'block', ...(typeof props.style === 'object' ? props.style : {}) }
      }
    >
      <path d="m2.5 8.5 4 4 7-9" />
    </svg>
  );
}

function HorizontalRuleIcon(props: JSX.IntrinsicElements['svg']) {
  return (
    <svg
      width="12"
      height="12"
      viewBox="0 0 24 24"
      fill="currentColor"
      stroke-width={1}
      {...props}
      style={
        typeof props.style === 'string'
          ? `display: block; ${props.style}`
          : { display: 'block', ...(typeof props.style === 'object' ? props.style : {}) }
      }
    >
      <line
        x1="3"
        y1="12"
        x2="21"
        y2="12"
        stroke="currentColor"
        vector-effect="non-scaling-stroke"
      />
    </svg>
  );
}

API reference

Prop
Type
Default
defaultValuestring[]—
Names of the checkboxes in the group that should be initially ticked. To render a controlled checkbox group, use the value prop instead.string[]
valuestring[]—
Names of the checkboxes in the group that should be ticked. To render an uncontrolled checkbox group, use the defaultValue prop instead.string[]
onValueChangefunction—
Event handler called when a checkbox in the group is ticked or unticked. Provides the new value as an argument.((value: string[], eventDetails: CheckboxGroup.ChangeEventDetails) => void)
allValuesstring[]—
Names of all checkboxes in the group. Use this when creating a parent checkbox.string[]
disabledbooleanfalse
Whether the component should ignore user interaction.boolean
classfunction—
CSS class applied to the element, or a function that returns a class based on the component’s state.JSX.ClassValue | ((state) => JSX.ClassValue)
stylefunction—
Style applied to the element, or a function that returns a style object based on the component’s state.JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)
renderfunction—
Replace the default element with a tag name, component, or render function.keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)

Data attributes

Name
Type
Default
data-disabled-—
Present when the checkbox group is disabled.-
Attribute
Description
data-disabled
Present when the checkbox group is disabled.
CheckboxGroup.State
type CheckboxGroupState = {
  /** Whether the component should ignore user interaction. */
  disabled: boolean;
  /** Whether the field has been touched. */
  touched: boolean;
  /** Whether the field value has changed from its initial value. */
  dirty: boolean;
  /** Whether the field is valid. */
  valid: boolean | null;
  /** Whether the field has a value. */
  filled: boolean;
  /** Whether the field is focused. */
  focused: boolean;
};
CheckboxGroup.ChangeEventReason
type CheckboxGroupChangeEventReason = 'none';
CheckboxGroup.ChangeEventDetails
type CheckboxGroupChangeEventDetails = {
  /** The reason for the event. */
  reason: 'none';
  /** The native event associated with the custom event. */
  event: Event;
  /** Cancels Base UI from handling the event. */
  cancel: () => void;
  /** Allows the event to propagate in cases where Base UI will stop the propagation. */
  allowPropagation: () => void;
  /** Indicates whether the event has been canceled. */
  isCanceled: boolean;
  /** Indicates whether the event is allowed to propagate. */
  isPropagationAllowed: boolean;
  /** The element that triggered the event, if applicable. */
  trigger: Element | undefined;
};