Checkbox
Capturar input booleano. Totalmente accesible. Listo para teclado.
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}
Instalación
Copia y pega el siguiente código en tu proyecto.
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 };
Actualiza las rutas de importación para que coincidan con la estructura de tu proyecto.
Anatomía
1import { Checkbox } from '@/components/ui/checkbox';
1<Checkbox id="terms" />
Variantes
Predeterminado (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}
Con Etiqueta (With Label)
Acompañado de una etiqueta (Label) para proveer contexto extra.
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}
Deshabilitado (Disabled)
Estado en el que el usuario no puede interactuar con él.
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}
Referencia de API
Checkbox
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
onCheckedChange | (checked: boolean) => void | - | Handler cuando el estado cambia |
disabled | boolean | - | Evita interacción y reduce opacidad |
¿Encontraste algo que mejorar?
¿Notaste un error, tipografía o detalle faltante en esta página? Ayúdanos a mejorar la documentación abriendo un issue en GitHub.