Checkbox
Capture boolean input. Fully accessible. Keyboard-ready.
Vela Studio
INV-2043
$1,280
Northwind Labs
INV-2044
$460
Harbor Freight Co
INV-2045
$3,910
1import { Checkbox } from '@/components/ui/checkbox';23const invoices = [4 { id: 'INV-2043', customer: 'Vela Studio', amount: '$1,280', selected: true },5 { id: 'INV-2044', customer: 'Northwind Labs', amount: '$460', selected: false },6 { id: 'INV-2045', customer: 'Harbor Freight Co', amount: '$3,910', selected: false },7];89export function Default() {10 return (11 <div className="border-border bg-card w-full max-w-md rounded-xl border">12 {invoices.map((invoice) => (13 <div14 key={invoice.id}15 className="border-border flex items-center gap-3 border-b p-3 last:border-b-0"16 >17 <Checkbox aria-label={`Select invoice ${invoice.id}`} defaultChecked={invoice.selected} />18 <div className="min-w-0 flex-1">19 <p className="truncate text-sm font-medium">{invoice.customer}</p>20 <p className="text-muted-foreground text-xs">{invoice.id}</p>21 </div>22 <p className="text-sm tabular-nums">{invoice.amount}</p>23 </div>24 ))}25 </div>26 );27}
Installation
Copy and paste the following code into your project.
1'use client';23import * as React from 'react';4import { cn } from '../lib/cn';56type IconProps = React.SVGProps<SVGSVGElement> & {7 size?: number | string;8};910function CheckIcon({ size = 24, strokeWidth = 1.5, ...props }: IconProps) {11 return (12 <svg13 xmlns="http://www.w3.org/2000/svg"14 width={size}15 height={size}16 viewBox="0 0 24 24"17 fill="none"18 stroke="currentColor"19 strokeWidth={strokeWidth}20 strokeLinecap="round"21 strokeLinejoin="round"22 aria-hidden="true"23 {...props}24 >25 <path d="m4 12.5 5 5L20 6.5" />26 </svg>27 );28}2930function MinusIcon({ size = 24, strokeWidth = 1.5, ...props }: IconProps) {31 return (32 <svg33 xmlns="http://www.w3.org/2000/svg"34 width={size}35 height={size}36 viewBox="0 0 24 24"37 fill="none"38 stroke="currentColor"39 strokeWidth={strokeWidth}40 strokeLinecap="round"41 strokeLinejoin="round"42 aria-hidden="true"43 {...props}44 >45 <path d="M5 12h14" />46 </svg>47 );48}4950export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {51 onCheckedChange?: (checked: boolean) => void;52 indeterminate?: boolean;53}5455function Checkbox({56 className,57 onCheckedChange,58 onChange,59 indeterminate = false,60 ref,61 ...props62}: CheckboxProps & { ref?: React.Ref<HTMLInputElement> }) {63 const innerRef = React.useRef<HTMLInputElement>(null);6465 const mergedRef = React.useCallback(66 (node: HTMLInputElement | null) => {67 innerRef.current = node;68 if (typeof ref === 'function') ref(node);69 else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = node;70 },71 [ref],72 );7374 React.useEffect(() => {75 if (innerRef.current) {76 innerRef.current.indeterminate = indeterminate;77 }78 }, [indeterminate]);7980 return (81 <div82 role="presentation"83 className={cn(84 "relative flex shrink-0 items-center justify-center before:absolute before:-inset-2 before:content-['']",85 className,86 )}87 onClick={(event) => {88 if (event.target !== event.currentTarget) return;89 if (event.currentTarget.closest('label')) return;90 innerRef.current?.click();91 }}92 >93 <input94 type="checkbox"95 ref={mergedRef}96 className="peer focus-visible:ring-ring border-primary checked:bg-primary checked:text-primary-foreground text-primary-foreground bg-background indeterminate:bg-primary ring-offset-background relative size-4 shrink-0 cursor-pointer appearance-none rounded-sm border transition-all focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-40"97 onChange={(e) => {98 onChange?.(e);99 onCheckedChange?.(e.target.checked);100 }}101 {...props}102 />103 <MinusIcon104 aria-hidden="true"105 className="text-primary-foreground pointer-events-none absolute size-3 opacity-0 peer-indeterminate:opacity-100"106 size={12}107 />108 <CheckIcon109 aria-hidden="true"110 className="text-primary-foreground pointer-events-none absolute size-3 opacity-0 transition-opacity peer-checked:opacity-100 peer-indeterminate:opacity-0"111 size={12}112 />113 </div>114 );115}116Checkbox.displayName = 'Checkbox';117118export { Checkbox };
Update the import paths to match your project structure.
Anatomy
1import { Checkbox } from '@/components/ui/checkbox';
1<Checkbox id="terms" />
Variants
Default
Vela Studio
INV-2043
$1,280
Northwind Labs
INV-2044
$460
Harbor Freight Co
INV-2045
$3,910
1import { Checkbox } from '@/components/ui/checkbox';23const invoices = [4 { id: 'INV-2043', customer: 'Vela Studio', amount: '$1,280', selected: true },5 { id: 'INV-2044', customer: 'Northwind Labs', amount: '$460', selected: false },6 { id: 'INV-2045', customer: 'Harbor Freight Co', amount: '$3,910', selected: false },7];89export function Default() {10 return (11 <div className="border-border bg-card w-full max-w-md rounded-xl border">12 {invoices.map((invoice) => (13 <div14 key={invoice.id}15 className="border-border flex items-center gap-3 border-b p-3 last:border-b-0"16 >17 <Checkbox aria-label={`Select invoice ${invoice.id}`} defaultChecked={invoice.selected} />18 <div className="min-w-0 flex-1">19 <p className="truncate text-sm font-medium">{invoice.customer}</p>20 <p className="text-muted-foreground text-xs">{invoice.id}</p>21 </div>22 <p className="text-sm tabular-nums">{invoice.amount}</p>23 </div>24 ))}25 </div>26 );27}
With Label
Checkbox component alongside a label for context.
Email me when
Sent to you and to the on call channel.
One email per billing period.
Every Monday at 9:00.
1import { Checkbox } from '@/components/ui/checkbox';2import { Label } from '@/components/ui/label';34const alerts = [5 {6 id: 'deploy-failed',7 label: 'A deploy fails',8 hint: 'Sent to you and to the on call channel.',9 defaultChecked: true,10 },11 {12 id: 'usage-limit',13 label: 'Usage passes 80% of the plan limit',14 hint: 'One email per billing period.',15 defaultChecked: true,16 },17 {18 id: 'weekly-report',19 label: 'The weekly traffic report is ready',20 hint: 'Every Monday at 9:00.',21 defaultChecked: false,22 },23];2425export function WithLabel() {26 return (27 <div className="flex w-full max-w-md flex-col gap-4">28 <p className="text-sm font-medium">Email me when</p>29 {alerts.map((alert) => (30 <div key={alert.id} className="flex items-start gap-2.5">31 <Checkbox id={alert.id} defaultChecked={alert.defaultChecked} className="mt-0.5" />32 <div className="flex flex-col gap-1">33 <Label htmlFor={alert.id}>{alert.label}</Label>34 <span className="text-muted-foreground text-xs">{alert.hint}</span>35 </div>36 </div>37 ))}38 </div>39 );40}
Disabled
A disabled state stops interactions.
Workspace security
Enforced by your identity provider.
Available on the Enterprise plan.
1import { Checkbox } from '@/components/ui/checkbox';2import { Label } from '@/components/ui/label';34const policies = [5 {6 id: 'policy-2fa',7 label: 'Require two factor authentication',8 hint: 'Enforced by your identity provider.',9 defaultChecked: true,10 },11 {12 id: 'policy-saml',13 label: 'Sign in with SAML only',14 hint: 'Available on the Enterprise plan.',15 defaultChecked: false,16 },17];1819export function Disabled() {20 return (21 <div className="flex w-full max-w-md flex-col gap-4">22 <p className="text-sm font-medium">Workspace security</p>23 {policies.map((policy) => (24 <div key={policy.id} className="flex items-start gap-2.5">25 <Checkbox26 id={policy.id}27 defaultChecked={policy.defaultChecked}28 className="mt-0.5"29 disabled30 />31 <div className="flex flex-col gap-1">32 <Label htmlFor={policy.id} className="opacity-40">33 {policy.label}34 </Label>35 <span className="text-muted-foreground text-xs">{policy.hint}</span>36 </div>37 </div>38 ))}39 </div>40 );41}
API Reference
Checkbox
| Prop | Type | Default | Description |
|---|---|---|---|
onCheckedChange | (checked: boolean) => void | - | Callback when checked state changes |
disabled | boolean | - | Disables the checkbox |
Found something to improve?
Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.