Checkbox
Checkboxes allow users to select multiple items from a list of individual items, or to mark one individual item as selected.
Usage
import { Checkbox } from '@lenso/ui';"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox } from "@lenso/ui";
export function Basic() { return ( <Checkbox name="basic-terms"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Accept terms and conditions </Checkbox.Content> </Checkbox> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Anatomy
import { Checkbox, Description, FieldError } from '@lenso/ui';
export default () => ( <Checkbox> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Label {/* plain text — the clickable label + accessible name */} </Checkbox.Content> <Description /> {/* Optional — field-level help text */} <FieldError /> {/* Optional — validation message */} </Checkbox>);Examples
Variants
The Checkbox component supports two visual variants:
primary(default) - Standard styling with default background, suitable for most use casessecondary- Lower emphasis variant, suitable for use in Surface components
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox, Description, TextField } from "@lenso/ui";import { checkboxSupportingStyles } from "@lenso/tokens/checkbox";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ root: { display: "flex", flexDirection: "column", gap: "1rem" }, section: { display: "flex", flexDirection: "column", gap: ".5rem" }, heading: { fontSize: ".875rem", fontWeight: 500, color: "var(--muted)" },});export function Variants() { return ( <div {...stylex.props(styles.root)}> <div {...stylex.props(styles.section)}> <p {...stylex.props(styles.heading)}>Primary variant</p> <TextField> <Checkbox id="primary" name="primary" variant="primary"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Primary checkbox </Checkbox.Content> <Description xstyle={checkboxSupportingStyles.direct}> Standard styling with default background </Description> </Checkbox> </TextField> </div> <div {...stylex.props(styles.section)}> <p {...stylex.props(styles.heading)}>Secondary variant</p> <TextField> <Checkbox id="secondary" name="secondary" variant="secondary"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Secondary checkbox </Checkbox.Content> <Description xstyle={checkboxSupportingStyles.direct}> Lower emphasis variant for use in surfaces </Description> </Checkbox> </TextField> </div> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Full Rounded
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox } from "@lenso/ui";import { labelStyles } from "@lenso/tokens/label";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ root: { display: "flex", flexDirection: "column", gap: "1.5rem" }, section: { display: "flex", flexDirection: "column", gap: ".75rem" }, label: { color: "var(--muted)" }, smallIndicator: { "--checkbox-checkmark-size": ".5rem" }, extraLargeIndicator: { "--checkbox-checkmark-size": "1rem" }, small: { width: ".75rem", height: ".75rem", borderRadius: "9999px", "::before": { borderRadius: "9999px" }, }, medium: { width: "1rem", height: "1rem", borderRadius: "9999px", "::before": { borderRadius: "9999px" }, }, large: { width: "1.25rem", height: "1.25rem", borderRadius: "9999px", "::before": { borderRadius: "9999px" }, }, extraLarge: { width: "1.5rem", height: "1.5rem", borderRadius: "9999px", "::before": { borderRadius: "9999px" }, },});export function FullRounded() { return ( <div {...stylex.props(styles.root)}> <div {...stylex.props(styles.section)}> <span {...stylex.props(labelStyles.label, styles.label)}>Rounded checkboxes</span> <Checkbox name="small-rounded"> <Checkbox.Content> <Checkbox.Control xstyle={styles.small}> <Checkbox.Indicator xstyle={styles.smallIndicator} /> </Checkbox.Control> Small size </Checkbox.Content> </Checkbox> </div> <div {...stylex.props(styles.section)}> <Checkbox name="default-rounded"> <Checkbox.Content> <Checkbox.Control xstyle={styles.medium}> <Checkbox.Indicator /> </Checkbox.Control> Default size </Checkbox.Content> </Checkbox> </div> <div {...stylex.props(styles.section)}> <Checkbox name="large-rounded"> <Checkbox.Content> <Checkbox.Control xstyle={styles.large}> <Checkbox.Indicator /> </Checkbox.Control> Large size </Checkbox.Content> </Checkbox> </div> <div {...stylex.props(styles.section)}> <Checkbox name="xl-rounded"> <Checkbox.Content> <Checkbox.Control xstyle={styles.extraLarge}> <Checkbox.Indicator xstyle={styles.extraLargeIndicator} /> </Checkbox.Control> Extra large size </Checkbox.Content> </Checkbox> </div> </div> );}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, Description, TextField } from "@lenso/ui";import { checkboxSupportingStyles } from "@lenso/tokens/checkbox";export function Disabled() { return ( <TextField> <Checkbox disabled id="feature" aria-describedby="feature-help"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Premium Feature </Checkbox.Content> <Description id="feature-help" xstyle={checkboxSupportingStyles.direct}> This feature is coming soon </Description> </Checkbox> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
External Label
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox } from "@lenso/ui";import { labelStyles } from "@lenso/tokens/label";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ root: { display: "flex", alignItems: "center", gap: ".75rem" } });export function ExternalLabel() { return ( <div {...stylex.props(styles.root)}> <Checkbox id="label-marketing"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> </Checkbox.Content> </Checkbox> <label {...stylex.props(labelStyles.label)} htmlFor="label-marketing"> Send me marketing emails </label> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
With Description
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox, Description, TextField } from "@lenso/ui";import { checkboxSupportingStyles } from "@lenso/tokens/checkbox";export function WithDescription() { return ( <TextField> <Checkbox name="description-notifications" aria-describedby="description-notifications-help"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Email notifications </Checkbox.Content> <Description id="description-notifications-help" xstyle={checkboxSupportingStyles.direct}> Get notified when someone mentions you in a comment </Description> </Checkbox> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Default Selected
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox } from "@lenso/ui";export function DefaultSelected() { return ( <Checkbox defaultChecked id="default-notifications"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Enable email notifications </Checkbox.Content> </Checkbox> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Invalid
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox, FieldError, TextField } from "@lenso/ui";import { checkboxSupportingStyles } from "@lenso/tokens/checkbox";export function Invalid() { return ( <TextField invalid> <Checkbox required name="agreement"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> I agree to the terms </Checkbox.Content> <FieldError match xstyle={[checkboxSupportingStyles.direct, checkboxSupportingStyles.error]} > You must accept the terms to continue </FieldError> </Checkbox> </TextField> );}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 } from "@lenso/ui";import { useState } from "react";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ root: { display: "flex", flexDirection: "column", gap: ".75rem" }, status: { fontSize: ".875rem", color: "var(--muted)" }, strong: { fontWeight: 500 },});export function Controlled() { const [isSelected, setIsSelected] = useState(true); return ( <div {...stylex.props(styles.root)}> <Checkbox id="email-notifications" checked={isSelected} onCheckedChange={setIsSelected}> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Email notifications </Checkbox.Content> </Checkbox> <p {...stylex.props(styles.status)}> Status: <span {...stylex.props(styles.strong)}>{isSelected ? "Enabled" : "Disabled"}</span> </p> </div> );}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, Description, TextField } from "@lenso/ui";import { checkboxSupportingStyles } from "@lenso/tokens/checkbox";import { useState } from "react";export function Indeterminate() { const [isIndeterminate, setIsIndeterminate] = useState(true); const [isSelected, setIsSelected] = useState(false); return ( <TextField> <Checkbox id="select-all" indeterminate={isIndeterminate} checked={isSelected} onCheckedChange={(selected) => { setIsSelected(selected); setIsIndeterminate(false); }} > <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Select all </Checkbox.Content> <Description xstyle={checkboxSupportingStyles.direct}> Shows indeterminate state (dash icon) </Description> </Checkbox> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Form Integration
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Button, Checkbox } from "@lenso/ui";import type { FormEvent } from "react";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ form: { display: "flex", flexDirection: "column", gap: "1rem" }, items: { display: "flex", flexDirection: "column", gap: ".75rem" }, submit: { marginTop: "1rem" },});export function Form() { const handleSubmit = (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); const formData = new FormData(e.currentTarget); alert( `Form submitted with:\n${Array.from(formData.entries()) .map(([key, value]) => `${key}: ${value}`) .join("\n")}`, ); }; return ( <form {...stylex.props(styles.form)} onSubmit={handleSubmit}> <div {...stylex.props(styles.items)}> <Checkbox name="notifications" value="on"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Enable notifications </Checkbox.Content> </Checkbox> <Checkbox defaultChecked name="newsletter" value="on"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Subscribe to newsletter </Checkbox.Content> </Checkbox> <Checkbox name="marketing" value="on"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Receive marketing updates </Checkbox.Content> </Checkbox> </div> <Button xstyle={styles.submit} size="sm" type="submit" variant="primary"> Submit </Button> </form> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Render Props
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox, Description, TextField } from "@lenso/ui";import { checkboxSupportingStyles } from "@lenso/tokens/checkbox";export function RenderProps() { return ( <TextField> <Checkbox id="render-props-terms" render={(props, { checked }) => ( <span {...props}> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> {checked ? "Terms accepted" : "Accept terms"} </Checkbox.Content> <Description xstyle={checkboxSupportingStyles.direct}> {checked ? "Thank you for accepting" : "Please read and accept the terms"} </Description> </span> )} /> </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 } from "@lenso/ui";export function RenderFunction() { return ( <Checkbox render={(props) => <div {...props} data-custom="bar" />}> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator /> </Checkbox.Control> Accept terms and conditions </Checkbox.Content> </Checkbox> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Custom Indicator
"use client";/** Adapted from HeroUI v3.2.6. SPDX-License-Identifier: Apache-2.0 */import { Checkbox } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ root: { display: "flex", gap: "1rem" } });export function CustomIndicator() { return ( <div {...stylex.props(styles.root)}> <Checkbox defaultChecked name="heart"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator render={(props, { checked }) => ( <span {...props}> {checked ? ( <svg aria-hidden="true" fill="currentColor" viewBox="0 0 24 24"> <path d="M12.62 20.81c-.34.12-.9.12-1.24 0C8.48 19.82 2 15.69 2 8.69 2 5.6 4.49 3.1 7.56 3.1c1.82 0 3.43.88 4.44 2.24a5.53 5.53 0 0 1 4.44-2.24C19.51 3.1 22 5.6 22 8.69c0 7-6.48 11.13-9.38 12.12Z" /> </svg> ) : null} </span> )} /> </Checkbox.Control> Heart </Checkbox.Content> </Checkbox> <Checkbox defaultChecked name="plus"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator render={(props, { checked }) => ( <span {...props}> {checked ? ( <svg aria-hidden="true" fill="none" viewBox="0 0 24 24"> <path d="M6 12H18" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="3" /> <path d="M12 18V6" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="3" /> </svg> ) : null} </span> )} /> </Checkbox.Control> Plus </Checkbox.Content> </Checkbox> <Checkbox indeterminate name="indeterminate"> <Checkbox.Content> <Checkbox.Control> <Checkbox.Indicator render={(props, { indeterminate }) => ( <span {...props}> {indeterminate ? ( <svg aria-hidden="true" stroke="currentColor" strokeWidth={3} viewBox="0 0 24 24" > <line x1="21" x2="3" y1="12" y2="12" /> </svg> ) : null} </span> )} /> </Checkbox.Control> Indeterminate </Checkbox.Content> </Checkbox> </div> );}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 } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ 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)" },});export function CustomStyles() { return ( <Checkbox id="custom"> <Checkbox.Content> <Checkbox.Control xstyle={styles.control}> <Checkbox.Indicator xstyle={styles.indicator} /> </Checkbox.Control> Custom styled checkbox </Checkbox.Content> </Checkbox> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Global CSS
To customize the Checkbox component classes, you can use the @layer components directive.
Learn more.
@layer components { .checkbox { @apply inline-flex gap-3 items-center; }
.checkbox__control { @apply size-5 border-2 border-gray-400 rounded data-[selected=true]:bg-blue-500 data-[selected=true]:border-blue-500;
/* Animated background indicator */ &::before { @apply bg-accent pointer-events-none absolute inset-0 z-0 origin-center scale-50 rounded-md opacity-0 content-[''];
transition: scale 200ms linear, opacity 200ms linear, background-color 200ms ease-out; }
/* Show indicator when selected */ &[data-selected="true"]::before { @apply scale-100 opacity-100; } }
.checkbox__indicator { @apply text-white; }
.checkbox__content { @apply items-center gap-3; }}Styling Reference
HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.
CSS Classes
The Checkbox component uses these CSS classes (View source styles):
Base Classes [!toc]
.checkbox- Base checkbox container (the field).checkbox__content- Clickable label wrapping the control and label text.checkbox__control- Checkbox control box.checkbox__indicator- Checkbox checkmark indicator
Interactive States
The checkbox supports both CSS pseudo-classes and data attributes for flexibility:
- Selected:
[data-selected="true"]or[aria-checked="true"](shows checkmark and background color change) - Indeterminate:
[data-indeterminate="true"](shows indeterminate state with dash) - Invalid:
[data-invalid="true"]or[aria-invalid="true"](shows error state with danger colors) - Hover:
:hoveror[data-hovered="true"]onCheckbox.Control(button) - Focus:
:focus-visibleor[data-focus-visible="true"]on the button (shows focus ring on control) - Disabled:
[data-disabled="true"]on the field (reduced opacity, including help text) - Pressed:
:activeor[data-pressed="true"]
API Reference
Checkbox
Inherits from React Aria CheckboxField.
| Prop | Type | Default | Description |
|---|---|---|---|
isSelected | boolean | false | Whether the checkbox is checked |
defaultSelected | boolean | false | Whether the checkbox is checked by default (uncontrolled) |
isIndeterminate | boolean | false | Whether the checkbox is in an indeterminate state |
isDisabled | boolean | false | Whether the checkbox is disabled |
isInvalid | boolean | false | Whether the checkbox is invalid |
isReadOnly | boolean | false | Whether the checkbox is read only |
isRequired | boolean | false | Whether the checkbox must be selected |
validate | (value: boolean) => ValidationError | true | null | undefined | - | Custom validation function |
validationBehavior | 'native' | 'aria' | 'native' | Whether to use native HTML form validation or ARIA |
variant | "primary" | "secondary" | "primary" | Visual variant of the component. primary is the default style with shadow. secondary is a lower emphasis variant without shadow, suitable for use in surfaces. |
name | string | - | The name of the input element, used when submitting an HTML form |
value | string | - | The value of the input element, used when submitting an HTML form |
onChange | (isSelected: boolean) => void | - | Handler called when the checkbox value changes |
children | React.ReactNode | (values: CheckboxFieldRenderProps) => React.ReactNode | - | Checkbox content or field render prop |
render | DOMRenderFunction<keyof React.JSX.IntrinsicElements, CheckboxFieldRenderProps> | - | Overrides the default DOM element with a custom render function. |
Checkbox.Content
The clickable <label> that wraps the control and label text. Put Checkbox.Control and the Label inside it; keep Description/FieldError as siblings of Checkbox.Content. For a checkbox with no label, omit the Label and pass an aria-label on Checkbox.
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | (values: CheckboxButtonRenderProps) => React.ReactNode | - | Button content (control + label), or a button render prop |
className | string | (values: CheckboxButtonRenderProps) => string | - | Classes applied to the clickable label |
CheckboxFieldRenderProps
When using a render prop on the root Checkbox, these field-level values are provided:
| Prop | Type | Description |
|---|---|---|
isSelected | boolean | Whether the checkbox is currently checked |
isIndeterminate | boolean | Whether the checkbox is in an indeterminate state |
isDisabled | boolean | Whether the checkbox is disabled |
isReadOnly | boolean | Whether the checkbox is read only |
isInvalid | boolean | Whether the checkbox is invalid |
isRequired | boolean | Whether the checkbox is required |
CheckboxButtonRenderProps
Checkbox.Control and Checkbox.Indicator use button-level render props (isHovered, isPressed, isFocusVisible, etc.). Pass a function as Checkbox.Control children or to Checkbox.Indicator to access them.