Checkbox
A checkbox component for selecting multiple options from a list. Supports labels, controlled and uncontrolled modes.
Installation
Terminal
TSXReactTailwind
npx @uiblox/cli add checkboxVisual Variations
Toggle between three different visual styles: default (square), rounded (circular), and filled (background).
Accept terms
Click to toggle
Newsletter
Pre-selected option
Locked option
Non-interactive
Basic Usage
Copy & paste ready
import { Checkbox } from "@/components/ui";
const [checked, setChecked] = useState(false);
<Checkbox
checked={checked}
onCheckedChange={setChecked}
label="Accept terms and conditions"
/>States
Copy & paste ready
<Checkbox label="Unchecked" />
<Checkbox label="Checked" defaultChecked />
<Checkbox label="Disabled" disabled />
<Checkbox label="Disabled Checked" disabled defaultChecked />Without Label
Copy & paste ready
<Checkbox />
<Checkbox defaultChecked />Form Example
Notification Preferences
Copy & paste ready
<div className="space-y-4 p-4 border rounded-lg">
<h3 className="font-medium">Notification Preferences</h3>
<div className="space-y-2">
<Checkbox label="Email notifications" defaultChecked />
<Checkbox label="Push notifications" />
<Checkbox label="SMS notifications" />
<Checkbox label="Marketing emails" />
</div>
</div>Accessibility
The Checkbox component uses a native input for full accessibility support, with custom styling that maintains all assistive technology features.
Keyboard Support
Space- Toggle checkbox stateTab- Move focus to/from checkbox
Built-in Features
- ✓Native checkbox with
sr-onlyfor screen readers - ✓Automatic label association via
htmlFor - ✓Focus-visible ring for keyboard users
Accessible Checkbox Patterns
// Basic accessible checkbox (label prop handles association)
<Checkbox
label="I agree to the terms and conditions"
/>
// Required checkbox with description
<div className="space-y-1">
<Checkbox
id="terms"
label="Accept terms"
aria-describedby="terms-description"
aria-required="true"
/>
<p id="terms-description" className="text-sm text-[#94a3b8] ml-6">
You must accept our terms to continue.
</p>
</div>
// Checkbox group with fieldset for screen readers
<fieldset>
<legend className="text-sm font-medium text-white mb-3">
Notification Preferences
</legend>
<div className="space-y-2" role="group" aria-label="Notification options">
<Checkbox label="Email notifications" name="notifications" />
<Checkbox label="Push notifications" name="notifications" />
<Checkbox label="SMS notifications" name="notifications" />
</div>
</fieldset>
// Error state
<div className="space-y-1">
<Checkbox
label="I accept the privacy policy"
aria-invalid="true"
aria-describedby="checkbox-error"
/>
<p id="checkbox-error" className="text-sm text-red-400 ml-6" role="alert">
You must accept the privacy policy to continue.
</p>
</div>
// Without visible label (use aria-label)
<Checkbox
aria-label="Select row for John Doe"
/>Best Practices
- • Always provide a label (visible or via
aria-label) - • Group related checkboxes with
<fieldset>and<legend> - • Use
aria-describedbyto link helper text or errors - • Clicking the label should toggle the checkbox
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| checked | boolean | - | Controlled checked state |
| defaultChecked | boolean | false | Default checked state for uncontrolled usage |
| onCheckedChange | (checked: boolean) => void | - | Callback when checked state changes |
| label | string | - | Label text displayed next to checkbox |
| disabled | boolean | false | Whether the checkbox is disabled |
Source Code
Copy this code into src/components/ui/checkbox.tsx:
checkbox.tsx
TSXReactTailwind
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange" | "type"> {
onCheckedChange?: (checked: boolean) => void;
label?: string;
}
const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
({ className, checked, defaultChecked, onCheckedChange, label, disabled, id, ...props }, ref) => {
const [internalChecked, setInternalChecked] = React.useState(defaultChecked ?? false);
const isChecked = checked ?? internalChecked;
const generatedId = React.useId();
const inputId = id ?? generatedId;
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newChecked = e.target.checked;
if (checked === undefined) {
setInternalChecked(newChecked);
}
onCheckedChange?.(newChecked);
};
return (
<div className="flex items-center">
<div className="relative flex items-center">
<input
ref={ref}
type="checkbox"
id={inputId}
checked={isChecked}
onChange={handleChange}
disabled={disabled}
className="sr-only peer"
{...props}
/>
<div
className={cn(
"h-4 w-4 shrink-0 rounded border border-gray-300 dark:border-gray-600 transition-colors",
"peer-focus-visible:ring-2 peer-focus-visible:ring-primary-500 peer-focus-visible:ring-offset-2",
"peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
isChecked && "bg-primary-600 border-primary-600",
className
)}
>
{isChecked && (
<svg className="h-full w-full text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
)}
</div>
</div>
{label && (
<label
htmlFor={inputId}
className={cn(
"ml-2 text-sm font-medium text-gray-700 dark:text-gray-200",
disabled && "cursor-not-allowed opacity-50"
)}
>
{label}
</label>
)}
</div>
);
}
);
Checkbox.displayName = "Checkbox";
export { Checkbox };