CheckboxGroup
A checkbox group component for managing multiple checkbox selections
Usage
import { CheckboxGroup, Checkbox, Label, Description } from '@lenso/ui';"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox, CheckboxGroup, TextField } from "@lenso/ui";import { labelStyles } from "@lenso/tokens/label";import { descriptionStyles } from "@lenso/tokens/description";import { checkboxSupportingStyles } from "@lenso/tokens/checkbox";import { useId } from "react";import * as stylex from "@stylexjs/stylex";
const interests = [ { value: "coding", label: "Coding", description: "Love building software" }, { value: "design", label: "Design", description: "Enjoy creating beautiful interfaces" }, { value: "writing", label: "Writing", description: "Passionate about content creation" },];export function Basic() { const id = useId(); return ( <TextField name="interests"> <CheckboxGroup aria-labelledby={`${id}-label`} aria-describedby={`${id}-help`}> <span id={`${id}-label`} {...stylex.props(labelStyles.label)}> Select your interests </span> <span id={`${id}-help`} {...stylex.props(descriptionStyles.description)}> Choose all that apply </span> {interests.map((interest) => ( <Checkbox key={interest.value} value={interest.value} aria-label={interest.label} aria-describedby={`${id}-${interest.value}`} > <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> {interest.label} </Checkbox.Content> <span id={`${id}-${interest.value}`} {...stylex.props(descriptionStyles.description, checkboxSupportingStyles.direct)} > {interest.description} </span> </Checkbox> ))} </CheckboxGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Anatomy
import {CheckboxGroup, Checkbox, Label, Description, FieldError} from '@lenso/ui';
export default () => ( <CheckboxGroup name="interests"> <Label /> <Description /> {/* Optional */} <Checkbox value="option1"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Label {/* plain text — the clickable label */} </Checkbox.Content> <Description /> {/* Optional per-checkbox help text */} </Checkbox> <FieldError /> {/* Optional */} </CheckboxGroup>);Examples
In Surface
When used inside a Surface component, use variant="secondary" to apply the lower emphasis variant suitable for surface backgrounds.
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox, CheckboxGroup, Surface, TextField } from "@lenso/ui";import { labelStyles } from "@lenso/tokens/label";import { descriptionStyles } from "@lenso/tokens/description";import { checkboxSupportingStyles } from "@lenso/tokens/checkbox";import { useId } from "react";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ surface: { width: "100%", borderRadius: "1.5rem", padding: "1.5rem" },});export function OnSurface() { const labelId = useId(); return ( <Surface xstyle={styles.surface}> <TextField name="interests"> <CheckboxGroup variant="secondary" aria-labelledby={labelId} aria-describedby={`${labelId}-help`} > <span id={labelId} {...stylex.props(labelStyles.label)}> Select your interests </span> <span id={`${labelId}-help`} {...stylex.props(descriptionStyles.description)}> Choose all that apply </span> <Checkbox value="coding" aria-label="Coding" aria-describedby={`${labelId}-coding`}> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Coding </Checkbox.Content> <span id={`${labelId}-coding`} {...stylex.props(descriptionStyles.description, checkboxSupportingStyles.direct)} > Love building software </span> </Checkbox> <Checkbox value="design" aria-label="Design" aria-describedby={`${labelId}-design`}> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Design </Checkbox.Content> <span id={`${labelId}-design`} {...stylex.props(descriptionStyles.description, checkboxSupportingStyles.direct)} > Enjoy creating beautiful interfaces </span> </Checkbox> <Checkbox value="writing" aria-label="Writing" aria-describedby={`${labelId}-writing`}> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Writing </Checkbox.Content> <span id={`${labelId}-writing`} {...stylex.props(descriptionStyles.description, checkboxSupportingStyles.direct)} > Passionate about content creation </span> </Checkbox> </CheckboxGroup> </TextField> </Surface> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Disabled
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox, CheckboxGroup, TextField } from "@lenso/ui";import { labelStyles } from "@lenso/tokens/label";import { descriptionStyles } from "@lenso/tokens/description";import { checkboxSupportingStyles } from "@lenso/tokens/checkbox";import { useId } from "react";import * as stylex from "@stylexjs/stylex";export function Disabled() { const labelId = useId(); return ( <TextField name="disabled-features"> <CheckboxGroup disabled aria-labelledby={labelId} aria-describedby={`${labelId}-help`}> <span id={labelId} {...stylex.props(labelStyles.label)}> Features </span> <span id={`${labelId}-help`} {...stylex.props(descriptionStyles.description)}> Feature selection is temporarily disabled </span> <Checkbox value="feature1" aria-label="Feature 1" aria-describedby={`${labelId}-feature1`}> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Feature 1 </Checkbox.Content> <span id={`${labelId}-feature1`} {...stylex.props(descriptionStyles.description, checkboxSupportingStyles.direct)} > This feature is coming soon </span> </Checkbox> <Checkbox value="feature2" aria-label="Feature 2" aria-describedby={`${labelId}-feature2`}> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Feature 2 </Checkbox.Content> <span id={`${labelId}-feature2`} {...stylex.props(descriptionStyles.description, checkboxSupportingStyles.direct)} > This feature is coming soon </span> </Checkbox> </CheckboxGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Indeterminate
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox, CheckboxGroup } from "@lenso/ui";import { useState } from "react";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ children: { marginInlineStart: "1.5rem", display: "flex", flexDirection: "column", gap: ".5rem" },});export function Indeterminate() { const [selected, setSelected] = useState(["coding"]); const allOptions = ["coding", "design", "writing"]; return ( <div> <Checkbox indeterminate={selected.length > 0 && selected.length < allOptions.length} checked={selected.length === allOptions.length} name="select-all" onCheckedChange={(checked) => setSelected(checked ? allOptions : [])} > <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Select all </Checkbox.Content> </Checkbox> <div {...stylex.props(styles.children)}> <CheckboxGroup value={selected} onValueChange={setSelected}> <Checkbox value="coding"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Coding </Checkbox.Content> </Checkbox> <Checkbox value="design"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Design </Checkbox.Content> </Checkbox> <Checkbox value="writing"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Writing </Checkbox.Content> </Checkbox> </CheckboxGroup> </div> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Controlled
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox, CheckboxGroup, TextField } from "@lenso/ui";import { labelStyles } from "@lenso/tokens/label";import { useId, useState } from "react";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ root: { minWidth: "320px" }, summary: { marginBlock: "1rem", fontSize: ".875rem", color: "var(--muted)" },});export function Controlled() { const labelId = useId(); const [selected, setSelected] = useState(["coding", "design"]); return ( <TextField name="skills"> <CheckboxGroup aria-labelledby={labelId} xstyle={styles.root} value={selected} onValueChange={setSelected} > <span id={labelId} {...stylex.props(labelStyles.label)}> Your skills </span> <Checkbox value="coding"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Coding </Checkbox.Content> </Checkbox> <Checkbox value="design"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Design </Checkbox.Content> </Checkbox> <Checkbox value="writing"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Writing </Checkbox.Content> </Checkbox> <p {...stylex.props(styles.summary)}>Selected: {selected.join(", ") || "None"}</p> </CheckboxGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Validation
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Button, Checkbox, CheckboxGroup, FieldError, Form, TextField } from "@lenso/ui";import { labelStyles } from "@lenso/tokens/label";import { useId } from "react";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ form: { display: "flex", flexDirection: "column", gap: "1rem", paddingInline: "1rem" },});export function Validation() { const labelId = useId(); return ( <Form xstyle={styles.form} onSubmit={(e) => { e.preventDefault(); const values = new FormData(e.currentTarget).getAll("preferences"); alert(`Selected preferences: ${values.join(", ")}`); }} > <TextField name="preferences" validate={(value) => Array.isArray(value) && value.length > 0 ? null : "Please select at least one notification method." } > <CheckboxGroup aria-labelledby={labelId}> <span id={labelId} {...stylex.props(labelStyles.label)}> Preferences </span> <Checkbox value="email"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Email notifications </Checkbox.Content> </Checkbox> <Checkbox value="sms"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> SMS notifications </Checkbox.Content> </Checkbox> <Checkbox value="push"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Push notifications </Checkbox.Content> </Checkbox> </CheckboxGroup> <FieldError>Please select at least one notification method.</FieldError> </TextField> <Button type="submit">Submit</Button> </Form> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Features and Add-ons Example
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Bell, Comment, Envelope } from "@gravity-ui/icons";import { Checkbox, CheckboxGroup, TextField } from "@lenso/ui";import { labelStyles } from "@lenso/tokens/label";import { descriptionStyles } from "@lenso/tokens/description";import { useId } from "react";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ wrapper: { display: "flex", width: "100%", flexDirection: "column", alignItems: "center", gap: "2.5rem", paddingInline: "1rem", paddingBlock: "2rem", }, section: { display: "flex", width: "100%", minWidth: "320px", flexDirection: "column", gap: "1rem", }, items: { display: "flex", flexDirection: "column", gap: ".5rem" }, content: { position: "relative", display: "flex", width: "100%", flexDirection: "row", alignItems: "flex-start", justifyContent: "flex-start", gap: "1rem", borderRadius: "1.5rem", backgroundColor: { default: "var(--surface)", ":is([data-checked] *)": "color-mix(in oklab, var(--accent) 10%, transparent)", }, paddingInline: "1.25rem", paddingBlock: "1rem", transition: { default: "background-color 150ms ease", "@media (prefers-reduced-motion: reduce)": "none", }, }, control: { position: "absolute", insetInlineEnd: "1rem", top: ".75rem", width: "1.25rem", height: "1.25rem", borderRadius: "9999px", "::before": { borderRadius: "9999px" }, }, icon: { width: "1.25rem", height: "1.25rem", color: "var(--accent-soft-foreground)" }, copy: { display: "flex", flexDirection: "column", gap: ".25rem" },});export function FeaturesAndAddOns() { const labelId = useId(); const addOns = [ { description: "Receive updates via email", icon: Envelope, title: "Email Notifications", value: "email", }, { description: "Get instant SMS notifications", icon: Comment, title: "SMS Alerts", value: "sms", }, { description: "Browser and mobile push alerts", icon: Bell, title: "Push Notifications", value: "push", }, ]; return ( <div {...stylex.props(styles.wrapper)}> <section {...stylex.props(styles.section)}> <TextField name="notification-preferences"> <CheckboxGroup aria-labelledby={labelId} aria-describedby={`${labelId}-help`}> <span id={labelId} {...stylex.props(labelStyles.label)}> Notification preferences </span> <span id={`${labelId}-help`} {...stylex.props(descriptionStyles.description)}> Choose how you want to receive updates </span> <div {...stylex.props(styles.items)}> {addOns.map((addon) => ( <Checkbox key={addon.value} value={addon.value} variant="secondary" aria-label={addon.title} aria-describedby={`${labelId}-${addon.value}`} > <Checkbox.Content xstyle={styles.content}> <Checkbox.Control xstyle={styles.control}> <Checkbox.Indicator /> </Checkbox.Control> <addon.icon {...stylex.props(styles.icon)} aria-hidden="true" /> <div {...stylex.props(styles.copy)}> <span>{addon.title}</span> <span id={`${labelId}-${addon.value}`} {...stylex.props(descriptionStyles.description)} > {addon.description} </span> </div> </Checkbox.Content> </Checkbox> ))} </div> </CheckboxGroup> </TextField> </section> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
With Custom Indicator
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox, CheckboxGroup, TextField } from "@lenso/ui";import { labelStyles } from "@lenso/tokens/label";import { descriptionStyles } from "@lenso/tokens/description";import { checkboxSupportingStyles } from "@lenso/tokens/checkbox";import { useId } from "react";import * as stylex from "@stylexjs/stylex";export function WithCustomIndicator() { const labelId = useId(); return ( <TextField name="features"> <CheckboxGroup aria-labelledby={labelId} aria-describedby={`${labelId}-help`}> <span id={labelId} {...stylex.props(labelStyles.label)}> Features </span> <span id={`${labelId}-help`} {...stylex.props(descriptionStyles.description)}> Select the features you want </span> <Checkbox value="notifications" aria-label="Email notifications" aria-describedby={`${labelId}-notifications`} > <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator render={(props, { checked }) => ( <span {...props}> {checked ? ( <svg aria-hidden="true" fill="none" stroke="currentColor" strokeLinecap="round" strokeWidth={2} viewBox="0 0 24 24" > <path d="M6 18L18 6M6 6l12 12" /> </svg> ) : null} </span> )} /> </Checkbox.Control> Email notifications </Checkbox.Content> <span id={`${labelId}-notifications`} {...stylex.props(descriptionStyles.description, checkboxSupportingStyles.direct)} > Receive updates via email </span> </Checkbox> <Checkbox value="newsletter" aria-label="Newsletter" aria-describedby={`${labelId}-newsletter`} > <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator render={(props, { checked }) => ( <span {...props}> {checked ? ( <svg aria-hidden="true" fill="none" stroke="currentColor" strokeLinecap="round" strokeWidth={2} viewBox="0 0 24 24" > <path d="M6 18L18 6M6 6l12 12" /> </svg> ) : null} </span> )} /> </Checkbox.Control> Newsletter </Checkbox.Content> <span id={`${labelId}-newsletter`} {...stylex.props(descriptionStyles.description, checkboxSupportingStyles.direct)} > Get weekly newsletters </span> </Checkbox> </CheckboxGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Render Function
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox, CheckboxGroup, TextField } from "@lenso/ui";import { labelStyles } from "@lenso/tokens/label";import { descriptionStyles } from "@lenso/tokens/description";import { checkboxSupportingStyles } from "@lenso/tokens/checkbox";import { useId } from "react";import * as stylex from "@stylexjs/stylex";export function RenderFunction() { const labelId = useId(); return ( <TextField name="interests"> <CheckboxGroup aria-labelledby={labelId} aria-describedby={`${labelId}-help`} render={(props) => <div {...props} data-custom="foo" />} > <span id={labelId} {...stylex.props(labelStyles.label)}> Select your interests </span> <span id={`${labelId}-help`} {...stylex.props(descriptionStyles.description)}> Choose all that apply </span> <Checkbox value="coding" aria-label="Coding" aria-describedby={`${labelId}-coding`}> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Coding </Checkbox.Content> <span id={`${labelId}-coding`} {...stylex.props(descriptionStyles.description, checkboxSupportingStyles.direct)} > Love building software </span> </Checkbox> <Checkbox value="design" aria-label="Design" aria-describedby={`${labelId}-design`}> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Design </Checkbox.Content> <span id={`${labelId}-design`} {...stylex.props(descriptionStyles.description, checkboxSupportingStyles.direct)} > Enjoy creating beautiful interfaces </span> </Checkbox> <Checkbox value="writing" aria-label="Writing" aria-describedby={`${labelId}-writing`}> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Writing </Checkbox.Content> <span id={`${labelId}-writing`} {...stylex.props(descriptionStyles.description, checkboxSupportingStyles.direct)} > Passionate about content creation </span> </Checkbox> </CheckboxGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Customization
Tailwind CSS
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox, CheckboxGroup, TextField } from "@lenso/ui";import { labelStyles } from "@lenso/tokens/label";import { descriptionStyles } from "@lenso/tokens/description";import { useId } from "react";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ root: { gap: ".75rem" }, item: { marginTop: 0 }, control: { backgroundColor: "var(--success-soft)", "::before": { backgroundColor: { default: "var(--success)", ":is([data-slot='checkbox']:hover *)": "var(--success)", ":is([data-slot='checkbox'][data-invalid] *)": "var(--success)", }, }, }, indicator: { color: "var(--success-foreground)" },});const channels = [ { label: "Email", value: "email" }, { label: "SMS", value: "sms" }, { label: "Push", value: "push" },] as const;export function CustomStyles() { const labelId = useId(); return ( <TextField name="notification-channels"> <CheckboxGroup aria-labelledby={labelId} aria-describedby={`${labelId}-help`} xstyle={styles.root} defaultValue={["email"]} > <span id={labelId} {...stylex.props(labelStyles.label)}> Notification channels </span> <span id={`${labelId}-help`} {...stylex.props(descriptionStyles.description)}> Choose how we should reach you for account updates. </span> {channels.map(({ label, value }) => ( <Checkbox key={value} value={value} xstyle={styles.item}> <Checkbox.Content> <Checkbox.Control xstyle={styles.control}> <Checkbox.Indicator xstyle={styles.indicator} /> </Checkbox.Control> {label} </Checkbox.Content> </Checkbox> ))} </CheckboxGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Global CSS
To customize the CheckboxGroup component classes, you can use the @layer components directive.
Learn more.
@layer components { .checkbox-group { @apply flex flex-col gap-2; }}Styling Reference
HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.
CSS Classes
The CheckboxGroup component uses these CSS classes (View source styles):
Base Classes [!toc]
.checkbox-group- Base checkbox group container
API Reference
CheckboxGroup
Inherits from React Aria CheckboxGroup.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string[] | - | The current selected values (controlled) |
defaultValue | string[] | - | The default selected values (uncontrolled) |
onChange | (value: string[]) => void | - | Handler called when the selected values change |
isDisabled | boolean | false | Whether the checkbox group is disabled |
isRequired | boolean | false | Whether the checkbox group is required |
isReadOnly | boolean | false | Whether the checkbox group is read only |
isInvalid | boolean | false | Whether the checkbox group is in an invalid state |
name | string | - | The name of the checkbox group, used when submitting an HTML form |
children | React.ReactNode | (values: CheckboxGroupRenderProps) => React.ReactNode | - | Checkbox group content or render prop |
render | DOMRenderFunction<keyof React.JSX.IntrinsicElements, CheckboxGroupRenderProps> | - | Overrides the default DOM element with a custom render function. |
Render Props
When using the render prop pattern, these values are provided:
| Prop | Type | Description |
|---|---|---|
value | string[] | The currently selected values |
isDisabled | boolean | Whether the checkbox group is disabled |
isReadOnly | boolean | Whether the checkbox group is read only |
isInvalid | boolean | Whether the checkbox group is in an invalid state |
isRequired | boolean | Whether the checkbox group is required |