Skip to content

ComponentsForms

Checkbox

Lets users turn a single option on or off, or pick any number of options from a group. Supports descriptions, an indeterminate state, card layouts, validation and native form submission.

React AriaSource

Installation

pnpm dlx shadcn@latest add @desyne/checkbox

The CLI installs dependencies and any other components this one uses.

Usage

tsx
import { Checkbox, CheckboxGroup } from "@/components/ui/checkbox";
tsx
<Checkbox>Remember me</Checkbox>

<CheckboxGroup label="Notify me about" defaultValue={["mentions"]}>
  <Checkbox value="mentions">Mentions</Checkbox>
  <Checkbox value="comments">Comments</Checkbox>
</CheckboxGroup>

A standalone Checkbox owns a boolean (isSelected). Inside a CheckboxGroup, each checkbox needs a value and the group owns a string[] of the selected values.

When to use

  • Checkbox — independent options, any number of which can be on, usually confirmed with a submit button.
  • Switch — a single setting that takes effect immediately, like turning on dark mode.
  • Radio Group — exactly one choice from a small, visible set.
  • Toggle Button Group — compact multi-select inside toolbars.
  • List Box or Tag Group — multi-select from a long or dynamic list.

Anatomy

Checkbox is built on React Aria's CheckboxField and CheckboxButton. You only render Checkbox; it composes the parts for you:

tsx
<CheckboxGroup>                  {/* label, description, error message */}
  <Checkbox value="…">           {/* CheckboxField: <div>, owns state and validation */}
    {/* CheckboxButton: <label> wrapping a hidden <input type="checkbox"> */}
    {/*   box + children + description (Text slot="description") */}
    {/* FieldError: standalone checkboxes only */}
  </Checkbox>
</CheckboxGroup>
PartRendersNotes
Checkbox root (CheckboxField)<div data-slot="checkbox-field">Holds the checked state, name / value, validation and the ARIA wiring between the input, description and error. Carries data-selected, data-indeterminate, data-disabled, data-readonly, data-invalid, data-required.
CheckboxButton<label data-slot="checkbox"> + hidden <input type="checkbox">The clickable area: box, label and description. Receives className, style, render, children and hover handlers, and carries every interaction attribute.
Box<span data-slot="checkbox-indicator">Shows a check icon, or a minus icon when isIndeterminate. Sized by size.
Description<span slot="description">From the description prop. Rendered inside the label so the whole area stays clickable, and linked to the input with aria-describedby.
FieldError<div data-slot="field-error">Standalone checkboxes only: shows errorMessage or the browser message when validation fails. Inside a group, the group shows one error instead.
CheckboxGroup<div role="group">Wraps the label, description, the checkboxes and the error message.
Group label / description<span>Rendered from the group's label and description props.

Examples

States

Checked, unchecked and indeterminate are the three visual values. isInvalid turns the box red, isDisabled dims it and removes it from the tab order, and isReadOnly keeps it focusable but ignores changes.

Sizes

md (16px) is the default. sm fits dense tables and filters; lg also bumps the label to text-base for touch-first layouts.

With description

description adds secondary text under the label and aligns the box to the first line. Use it to explain the consequence of an option instead of a long label.

Controlled

Use isSelected and onChange to own the value, for example to show or persist it elsewhere. Use defaultSelected when you only need the initial value.

Two-factor is off.

Group

CheckboxGroup renders a shared label and description, gives the checkboxes role="group", and collects the selected values into an array.

Notify me aboutWe'll only email you about the things you pick.

Horizontal

orientation="horizontal" lays the checkboxes out in a wrapping row. Keep labels short; switch back to vertical as soon as options wrap to a second line.

Platforms

Disabled and read-only in a group

isDisabled on a single checkbox locks just that option; use the description to say why. isReadOnly on the group shows the current value without letting users change it, and keeps every option focusable.

Email alertsSecurity alerts are required by your organization.
Included in your plan

Controlled group

Pass value and onChange to the group to own the array of selected values.

Languages

Selected: typescript

Indeterminate

A parent checkbox for a set of children. Set isIndeterminate when only some children are selected. It's purely visual (announced as "mixed"), so you must control it; pressing the checkbox calls onChange with the next value.

Cards

className is merged with the base styles, so a border, padding and data-selected: styles turn a checkbox with a description into a selectable card. The whole card is the label, so it's all clickable.

Add-ons

Required and validation

isRequired on a group requires at least one selection; on a standalone checkbox it requires it to be checked. With the default native validation, submitting the form shows the errorMessage (or the browser message) under the group or the standalone checkbox and focuses the first invalid field. A required group also adds a * to its label.

How did you hear about us?

Custom validation

validate receives the selected values and returns an error string, or null when valid. With validationBehavior="aria" the error shows as soon as the value changes instead of on submit.

Topics to followChoose up to three.

Form submission

Give the group a name and every checked value is submitted under it; read them with FormData.getAll(). A standalone checkbox submits its value ("on" by default) only when checked.

Toppings

Recipes

Bulk selection

A select-all checkbox in a list header, indeterminate while only some rows are selected, with bulk actions that appear once anything is selected. Row checkboxes have no visible label, so each gets an aria-label.

  • Q3 revenue report.pdf2.4 MB
  • 2025 roadmap.key18.1 MB
  • Brand guidelines.fig7.8 MB
  • Vendor contract (signed).pdf312 KB

Notification matrix

A table of events × channels. Each checkbox gets an aria-label combining its row and column, since the visual headers aren't associated with it.

Notify me whenEmailPushSMS
Someone mentions you
An issue is assigned to you
A deploy fails
A new invoice is available

Onboarding checklist

A controlled group drives a ProgressBar. children is a render function, so completed steps are struck through.

Getting started1 of 4

Accessibility

  • Each checkbox is a native <input type="checkbox">, visually hidden inside a <label>, so clicking anywhere on the label toggles it and it works with every assistive technology and with native forms.
  • isIndeterminate sets the input's indeterminate property, which screen readers announce as "mixed".
  • Checkboxes without visible text (table rows, matrices) need an aria-label or aria-labelledby.
  • CheckboxGroup renders role="group" labelled by its label; its description and error message are linked with aria-describedby.
  • description on a Checkbox is linked to the input with aria-describedby, so it's announced as the description rather than as part of the name. It sits inside the <label> (so it's clickable) but is hidden from the name computation.
  • A standalone checkbox's error message is linked to the input with aria-describedby as well.
  • The focus ring only appears for keyboard focus (data-focus-visible).

Keyboard

KeyAction
TabMoves focus to the next checkbox. Every checkbox in a group is its own tab stop.
SpaceToggles the focused checkbox

Styling

Data attributes

The attributes you'll style against are on the <label> (CheckboxButton), which is also where className goes. Target the box or label text from it with group-data-*/checkbox:. The outer CheckboxField <div> repeats the state attributes (not the interaction ones) and is a group/checkbox-field, for styling siblings such as the error message.

Attribute<label> (CheckboxButton)<div> (CheckboxField)Present when
data-selectedYesYesChecked
data-indeterminateYesYesisIndeterminate is true
data-hoveredYesNoHovered with a mouse or pen
data-pressedYesNoBeing pressed
data-focused / data-focus-visibleYesNoFocused / focused with the keyboard
data-disabledYesYesDisabled, directly or through the group
data-readonlyYesYesRead-only, directly or through the group
data-invalidYesYesInvalid, directly or through the group
data-requiredYesYesRequired

On CheckboxGroup (children can use group-data-*/field:): data-disabled, data-readonly, data-required, data-invalid.

Slots

data-slotElement
checkbox-fieldEach checkbox's outer <div> (CheckboxField)
checkboxEach checkbox's <label> (CheckboxButton), where className goes
checkbox-indicatorThe box
checkbox-groupGroup root
label, description, field-errorGroup label, description and error message; description and field-error are also used on a standalone checkbox

Color

The box uses the --brand and --brand-foreground theme variables (red --destructive when invalid). Override them on a single checkbox or a whole group for a one-off color:

tsx
<Checkbox className="[--brand:var(--color-emerald-600)] [--brand-foreground:white]">
  Mark as paid
</Checkbox>

Render props

className and children accept a function of the CheckboxButton state (isSelected, isIndeterminate, isHovered, isPressed, isFocusVisible, isDisabled, isReadOnly, isInvalid, isRequired):

tsx
<Checkbox className={({ isSelected }) => (isSelected ? "font-medium" : "")}>
  {({ isIndeterminate }) => (isIndeterminate ? "Some selected" : "Select all")}
</Checkbox>

API Reference

Checkbox

Prop

Type

Also accepts every prop of React Aria's CheckboxField. className, style, render, children and the hover handlers go to the CheckboxButton instead; everything else (state, name, value, validation, focus and press handlers, ARIA and DOM attributes) goes to the CheckboxField.

CheckboxGroup

Prop

Type

Also accepts every prop of React Aria's CheckboxGroup.