Checkbox
A binary or indeterminate selection control. Supports label, description, three sizes, and an indeterminate state for select-all patterns.
AccessibleDark ModeIndeterminate3 SizesCustom Visual
01
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.Root label="Accept terms and conditions" /> // Controlled <Checkbox.Root checked={isChecked} onChange={setIsChecked} label="Email notifications" description="Receive updates via email." /> // Indeterminate (select-all pattern) <Checkbox.Root checked={allSelected} indeterminate={someSelected && !allSelected} onChange={handleSelectAll} label="Select all" /> // States <Checkbox.Root checked label="Checked" /> <Checkbox.Root indeterminate label="Indeterminate" /> <Checkbox.Root disabled label="Disabled" /> <Checkbox.Root checked disabled label="Checked + disabled" /> // Sizes <Checkbox.Root size="sm" label="Small" /> <Checkbox.Root size="md" label="Medium" /> <Checkbox.Root size="lg" label="Large" />
Backward compatible: API lama <Checkbox /> masih didukung, tapi docs sekarang pakai <Checkbox.Root />.
04
Copy-Paste (Single File)
Checkbox.tsx
import { useEffect, useRef } from "react"; type CheckboxSize = "sm" | "md" | "lg"; 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 cn = (...classes: Array<string | undefined | false>) => classes.filter(Boolean).join(" "); const BOX_SIZES: Record<CheckboxSize, string> = { sm: "h-4 w-4", md: "h-5 w-5", lg: "h-6 w-6", }; function CheckboxRoot({ 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 ? "border-primary bg-primary text-white" : "border-slate-300 bg-white dark:border-slate-600 dark:bg-[#0d1117]")}> {isChecked && <span className="text-[10px]">✓</span>} {indeterminate && !isChecked && <span className="text-[10px]">−</span>} </div> </div> {(label ?? description) && ( <div className="min-w-0"> {label && <span className="block text-sm font-medium leading-tight text-slate-900 dark:text-white">{label}</span>} {description && <p className="mt-0.5 text-xs leading-relaxed text-slate-500 dark:text-slate-400">{description}</p>} </div> )} </label> ); } type CheckboxCompound = typeof CheckboxRoot & { Root: typeof CheckboxRoot }; export const Checkbox = Object.assign(CheckboxRoot, { Root: CheckboxRoot }) as CheckboxCompound;
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. |