Installation
pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/checkbox.jsonInstalls to @/components/ui/checkbox.
Manual installation
Install the dependencies:
pnpm add @base-ui/react@^1.8.0 lucide-react@^1.48.0Copy the source into your project:
'use client'
import { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox'
import { CheckIcon, MinusIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
/**
* `readOnly` shows the value but prevents changing it. Unlike `disabled`, the checkbox stays focusable and is announced
* as read-only (`aria-readonly`). Base UI blocks the change and sets `data-readonly`; we also keep `data-read-only`.
*/
function Checkbox({ className, readOnly, ...props }: CheckboxPrimitive.Root.Props) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
data-read-only={readOnly || undefined}
readOnly={readOnly}
className={cn(
'peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input transition-colors outline-none group-has-disabled/field:opacity-50 group-has-[:focus-visible]/field-label:ring-0 group-has-[:focus-visible]/field-label:not-data-checked:border-input after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring data-disabled:cursor-not-allowed data-disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground data-indeterminate:border-primary data-indeterminate:bg-primary data-indeterminate:text-primary-foreground group-has-[:focus-visible]/field-label:data-checked:border-primary data-readonly:cursor-default data-readonly:border-dashed data-readonly:border-foreground/60 data-readonly:bg-muted/50 data-readonly:data-checked:border-foreground/60 data-readonly:data-checked:bg-muted/50 data-readonly:data-checked:text-foreground data-readonly:data-indeterminate:border-foreground/60 data-readonly:data-indeterminate:bg-muted/50 data-readonly:data-indeterminate:text-foreground',
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="grid place-content-center text-current transition-none [&>svg]:size-3.5"
>
{/* A minus for the indeterminate state, so it isn't shown as checked */}
<CheckIcon aria-hidden="true" className="in-data-indeterminate:hidden" />
<MinusIcon aria-hidden="true" className="hidden in-data-indeterminate:block" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)
}
export { Checkbox }A checkbox component that allows users to select one or multiple options. Built on top of Base UI Checkbox, it follows WAI-ARIA design patterns and is fully accessible.
Features
- Checked and unchecked states, controlled (
checked/onCheckedChange) or uncontrolled (defaultChecked) - Mixed state with the
indeterminateprop, shown with a minus - Disabled state
- Invalid state via
aria-invalid - Enlarged hit area around the 16px box for easier clicking
- Works with
Labelor theFieldcomponents for labels, descriptions and groups - Keyboard navigation and screen reader support
- Form integration (
name,value,required)
Pass readOnly to show a value that can't be changed. Unlike disabled, a read-only checkbox stays focusable and
is announced as read-only (aria-readonly), so screen reader users still hear its state.
Usage
import { Checkbox } from '@/components/ui/checkbox'
import { Label } from '@/components/ui/label'
export function MyCheckbox() {
return (
<div className="flex items-center gap-2">
<Checkbox id="confirm-details" />
<Label htmlFor="confirm-details">I confirm that the information is correct</Label>
</div>
)
}With Field
Use the Field components for a label and description.
import { Checkbox } from '@/components/ui/checkbox'
import { Field, FieldContent, FieldDescription, FieldLabel } from '@/components/ui/field'
export function CheckboxWithDescription() {
return (
<Field orientation="horizontal">
<Checkbox id="digital-mailbox" defaultChecked />
<FieldContent>
<FieldLabel htmlFor="digital-mailbox">Send decisions to my digital mailbox</FieldLabel>
<FieldDescription>
Decisions and letters arrive digitally instead of by post.
</FieldDescription>
</FieldContent>
</Field>
)
}Invalid
<Checkbox id="data-consent" aria-invalid />Examples
Default
A checkbox with a label.
With Description Text
A checkbox with additional descriptive text using Field, FieldContent, FieldLabel and FieldDescription.
Group
A group of checkboxes inside a FieldSet with a legend.
Select All
A parent checkbox that is indeterminate while only some of the rows are selected.
<Checkbox
checked={selected.length === cases.length}
indeterminate={selected.length > 0 && selected.length < cases.length}
onCheckedChange={(checked) => setSelected(checked ? cases.map((c) => c.id) : [])}
/>Choice Card
Wrap a horizontal Field in a FieldLabel to make the whole card clickable. The card is highlighted while checked.
Disabled
Checkboxes in their disabled state, both unchecked and checked. Explain why an option isn't available.
Read Only
Shows the value without letting it change, by mouse or keyboard. It stays focusable.
Invalid
A checkbox marked invalid with aria-invalid, with the error in a FieldError.
Form Example
Checkboxes in a form inside a Card.
Accessibility
The Checkbox component follows WAI-ARIA guidelines:
- Uses native
checkboxrole - Supports keyboard navigation:
Space: Toggle checkbox stateTab: Move focus to checkbox
- Provides proper labeling through
aria-labeloraria-labelledby - Indicates state through
aria-checked - Communicates disabled state via
aria-disabled - Maintains focus management
- Supports form validation
Best Practices
-
Labeling:
- Always provide a clear, descriptive label
- Use concise, action-oriented text
- Place labels to the right of checkboxes
- Consider additional description for complex options
-
Visual Design:
- Maintain sufficient spacing between checkboxes
- Ensure adequate touch target size
- Provide clear visual feedback for all states
- Use consistent styling across the application
-
Interaction:
- Group related checkboxes
- Use
FieldSetandFieldLegendfor groups - Provide clear error states with
aria-invalid
-
Implementation:
- Use semantic HTML
- Implement proper form validation
- Consider controlled vs. uncontrolled usage
- Test with keyboard and screen readers
Related Components
- Radio Group
- Switch
- Toggle
- Field
- Form