Checkbox
A binary or indeterminate selection control. Supports label, description, three sizes, and an indeterminate state for select-all patterns.
AccessibleDark ModeIndeterminate3 SizesCustom Visual
Install
$
npx react-principles add checkbox01
Theme Preview
All four states — unchecked, checked, indeterminate, and disabled — rendered with forced light and dark styling for direct comparison.
Light
Accept termsUnchecked
Email notificationsChecked
All categoriesIndeterminate
Archived itemsDisabled
Dark
Accept termsUnchecked
Email notificationsChecked
All categoriesIndeterminate
Archived itemsDisabled
02
Live Demo
Size
03
Code Snippet
src/ui/Checkbox.tsx
import { Checkbox } from "@/ui/Checkbox"; // Basic <Checkbox label="Accept terms and conditions" /> // Controlled <Checkbox checked={isChecked} onChange={setIsChecked} label="Email notifications" description="Receive updates via email." /> // Indeterminate (select-all pattern) <Checkbox checked={allSelected} indeterminate={someSelected && !allSelected} onChange={handleSelectAll} label="Select all" /> // States <Checkbox checked label="Checked" /> <Checkbox indeterminate label="Indeterminate" /> <Checkbox disabled label="Disabled" /> <Checkbox checked disabled label="Checked + disabled" /> // Sizes <Checkbox size="sm" label="Small" /> <Checkbox size="md" label="Medium" /> <Checkbox size="lg" label="Large" />
Backward compatible: API lama <Checkbox /> masih didukung, tapi docs sekarang pakai <Checkbox />.
04
Copy-Paste (Single File)
Checkbox.tsx
import { useRef, useEffect } from "react"; import { cn } from "@/lib/utils"; export type CheckboxSize = "sm" | "md" | "lg"; export interface CheckboxProps { checked?: boolean; defaultChecked?: boolean; indeterminate?: boolean; disabled?: boolean; size?: CheckboxSize; label?: string; description?: string; id?: string; name?: string; onChange?: (checked: boolean) => void; className?: string; } const BOX_SIZES: Record<CheckboxSize, string> = { sm: "h-4 w-4", md: "h-5 w-5", lg: "h-6 w-6", }; const ICON_SIZES: Record<CheckboxSize, string> = { sm: "h-2.5 w-2.5", md: "h-3 w-3", lg: "h-3.5 w-3.5", }; const LABEL_SIZES: Record<CheckboxSize, string> = { sm: "text-xs", md: "text-sm", lg: "text-base", }; function CheckIcon({ className }: { className?: string }) { return ( <svg className={className} viewBox="0 0 12 12" fill="none"> <path d="M2 6l3 3 5-5" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" /> </svg> ); } function MinusIcon({ className }: { className?: string }) { return ( <svg className={className} viewBox="0 0 12 12" fill="none"> <path d="M2.5 6h7" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" /> </svg> ); } export function Checkbox({ checked, defaultChecked, indeterminate = false, disabled = false, size = "md", label, description, id, name, onChange, className, }: CheckboxProps) { const inputRef = useRef<HTMLInputElement>(null); useEffect(() => { if (inputRef.current) { inputRef.current.indeterminate = indeterminate; } }, [indeterminate]); const isChecked = checked ?? false; const isFilled = isChecked || indeterminate; return ( <label className={cn( "inline-flex items-start gap-3", disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer", className, )} > <div className="relative mt-0.5 shrink-0"> <input ref={inputRef} type="checkbox" id={id} name={name} checked={checked} defaultChecked={defaultChecked} disabled={disabled} onChange={(e) => onChange?.(e.target.checked)} className="sr-only" /> <div className={cn( "flex items-center justify-center rounded-sm border-2 transition-all", BOX_SIZES[size], isFilled ? "bg-primary border-primary" : "bg-white dark:bg-[#0d1117] border-slate-300 dark:border-slate-600", !disabled && !isFilled && "hover:border-primary", )} > {isChecked && <CheckIcon className={cn("text-white", ICON_SIZES[size])} />} {indeterminate && !isChecked && <MinusIcon className={cn("text-white", ICON_SIZES[size])} />} </div> </div> {(label ?? description) && ( <div className="min-w-0"> {label && ( <span className={cn("block font-medium text-slate-900 dark:text-white leading-tight", LABEL_SIZES[size])}> {label} </span> )} {description && ( <p className="text-xs text-slate-500 dark:text-slate-400 mt-0.5 leading-relaxed"> {description} </p> )} </div> )} </label> ); }
05
Props
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | — | Controlled checked state. |
defaultChecked | boolean | false | Uncontrolled initial checked state. |
indeterminate | boolean | false | Shows a dash — used for partial selection in select-all patterns. |
disabled | boolean | false | Prevents interaction and reduces opacity. |
size | "sm" | "md" | "lg" | "md" | Controls box size and label font size. |
label | string | — | Clickable text label rendered beside the checkbox. |
description | string | — | Secondary muted text displayed below the label. |
onChange | (checked: boolean) => void | — | Callback fired with the new boolean value. |
id / name | string | — | Forwarded to the underlying <input> element. |