Checkbox
A control that toggles between checked and unchecked states.
Installation
$
Usage
import { Checkbox } from "@/components/ui/checkbox"Examples
Default
Wrap the box and its text in one Label so the whole row toggles, gap included. A label sitting next to the checkbox instead leaves a dead strip people click on and nothing happens.
Sizes
14, 16, and 20px. The check icon scales with the box, so size is the only thing you set.
With description
A second line under the label. Indent it past the box (pl-6 is the 16px box plus the 8px gap) so it lines up with the title rather than hanging under the checkbox.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "default" | "lg" | "default" | The checkbox size: sm (14px), default (16px), or lg (20px). The check icon scales to match. |
| checked | boolean | "indeterminate" | - | The controlled checked state. Use with onCheckedChange. |
| defaultChecked | boolean | - | The initial checked state when uncontrolled. |
| onCheckedChange | (checked: boolean | "indeterminate") => void | - | Called when the checked state changes. |
| disabled | boolean | false | Disables the checkbox and dims it to 70% opacity. |
"use client"
import * as React from "react"
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox"
import { IconCheck as CheckIcon, IconMinus as MinusIcon } from "@tabler/icons-react"
import { cn } from "@/registry/lib/utils"
// @use-when an independent on/off choice.
function Checkbox({
className,
size = "default",
...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root> & {
size?: "sm" | "default" | "lg"
}) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
data-size={size}
className={cn(
"peer group/checkbox tap-target inline-flex items-center justify-center border-border dark:bg-neutral-800 data-[checked]:bg-accent data-[checked]:text-accent-foreground dark:data-[checked]:bg-accent data-[checked]:border-accent focus-visible:border-accent focus-visible:ring-ring-accent aria-invalid:ring-ring-destructive aria-invalid:border-destructive shrink-0 rounded-sm border shadow-xs transition-[transform,color,background-color,border-color,box-shadow] duration-150 ease-out motion-reduce:transition-[color,background-color,border-color,box-shadow] active:scale-[0.98] outline-none focus-visible:ring-1 cursor-pointer data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70 data-[size=sm]:size-3.5 data-[size=default]:size-4 data-[size=lg]:size-5",
className
)}
{...props}
>
{/* Kept mounted (`keepMounted`) so the tick never unmounts; its show/hide
is a CSS transition on transform+opacity keyed off the root's checked
state, so enter and exit run the same 150ms path and a fast toggle
retargets from wherever it is. A keyframe enter (animate-in) restarted
from zero on every tick and had no exit at all. Indeterminate gets the
same treatment because the root drops `data-checked` for it. Under
reduced motion the scale is pinned and only the fade runs. */}
<CheckboxPrimitive.Indicator
keepMounted
data-slot="checkbox-indicator"
className="grid place-content-center text-current scale-50 opacity-0 transition-[transform,opacity] duration-150 ease-out group-data-[checked]/checkbox:scale-100 group-data-[checked]/checkbox:opacity-100 group-data-[indeterminate]/checkbox:scale-100 group-data-[indeterminate]/checkbox:opacity-100 motion-reduce:scale-100 motion-reduce:transition-[opacity]"
>
<CheckIcon className="group-data-[indeterminate]/checkbox:hidden group-data-[size=sm]/checkbox:size-2 group-data-[size=default]/checkbox:size-2.5 group-data-[size=lg]/checkbox:size-3" />
<MinusIcon className="hidden group-data-[indeterminate]/checkbox:block group-data-[size=sm]/checkbox:size-2 group-data-[size=default]/checkbox:size-2.5 group-data-[size=lg]/checkbox:size-3" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)
}
export { Checkbox }