Checkbox Group
Provides shared state to a series of checkboxes.
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
- Form controls must have an accessible name: It can be created using
<label>elements, or theFieldandFieldsetcomponents. See Labeling a checkbox group and the forms guide.
Anatomy
Checkbox Group is composed together with Checkbox. Import the components and place them together:
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:
<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:
<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).
<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:
<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:
<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:
- Make
<CheckboxGroup>a controlled component - Pass an array of all the child checkbox values to the
allValuesprop on the<CheckboxGroup>component - Add the
parentboolean 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
defaultValuestring[]—
value prop instead.string[]valuestring[]—
defaultValue prop instead.string[]onValueChangefunction—
((value: string[], eventDetails: CheckboxGroup.ChangeEventDetails) => void)allValuesstring[]—
string[]disabledbooleanfalse
booleanclassfunction—
JSX.ClassValue | ((state) => JSX.ClassValue)stylefunction—
JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)renderfunction—
keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)Data attributes
data-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;
};