{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"checkbox","type":"registry:component","title":"Checkbox","description":"A real checkbox whose fill grows from the centre and whose tick draws itself, with a mixed state that bends into the tick.","author":"Hairline UI","dependencies":[],"registryDependencies":["https://hairlineui.com/r/ui-interaction-motion.json?as=dependency"],"files":[{"path":"registry/checkbox.tsx","type":"registry:component","content":"// Hairline UI · checkbox · MIT License\n// Copyright (c) 2026 Hairline UI · https://hairlineui.com/license#components\n\n\"use client\";\n\nimport * as React from \"react\";\nimport { EASE_OUT, EASE_THROW, useInteractionReducedMotion } from \"./ui-interaction-motion\";\n\n/* ─────────────────────────────────────────────────────────\n * CHECKBOX: a tick that draws itself\n *\n *   check    the box fills from its centre (a slight overshoot)\n *            and the tick draws itself along its stroke\n *   uncheck  the tick retracts the way it came, the fill shrinks\n *   mixed    a dash grows from the middle; mixed → checked bends\n *            the dash into the tick\n *   press    the box gives under the finger\n *\n * A real <input type=\"checkbox\"> underneath, so forms, labels,\n * Space and screen readers all work as they always do.\n * ───────────────────────────────────────────────────────── */\n\nexport interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"type\" | \"size\" | \"checked\" | \"defaultChecked\"> {\n  checked?: boolean | \"mixed\";\n  defaultChecked?: boolean;\n  onCheckedChange?: (checked: boolean) => void;\n  label?: React.ReactNode;\n  description?: React.ReactNode;\n  /** 14px or 16px box. */\n  size?: \"sm\" | \"md\";\n  ref?: React.Ref<HTMLInputElement>;\n}\n\nexport function Checkbox({\n  checked,\n  defaultChecked = false,\n  onCheckedChange,\n  label,\n  description,\n  size = \"md\",\n  disabled,\n  className = \"\",\n  onChange,\n  ref,\n  id,\n  ...props\n}: CheckboxProps) {\n  const reduced = useInteractionReducedMotion();\n  const auto = React.useId();\n  const inputId = id ?? auto;\n  const inner = React.useRef<HTMLInputElement>(null);\n  const [own, setOwn] = React.useState(defaultChecked);\n  const state = checked !== undefined ? checked : own;\n  const on = state === true;\n  const mixed = state === \"mixed\";\n\n  React.useEffect(() => {\n    if (inner.current) inner.current.indeterminate = mixed;\n  }, [mixed]);\n\n  const box = size === \"sm\" ? \"size-3.5 rounded-[4px]\" : \"size-4 rounded-[5px]\";\n  const t = (ms: number, delay = 0, ease = EASE_OUT) => (reduced ? \"none\" : `${ms}ms ${ease} ${delay}ms`);\n\n  return (\n    <label\n      htmlFor={inputId}\n      className={`group/cb inline-flex select-none items-start gap-2.5 ${disabled ? \"cursor-default opacity-50\" : \"cursor-pointer\"} ${className}`}\n    >\n      <span className={`relative mt-[2px] inline-flex shrink-0 ${box}`}>\n        <input\n          ref={(el) => {\n            inner.current = el;\n            if (typeof ref === \"function\") ref(el);\n            else if (ref) (ref as React.RefObject<HTMLInputElement | null>).current = el;\n          }}\n          id={inputId}\n          type=\"checkbox\"\n          checked={on}\n          disabled={disabled}\n          aria-checked={mixed ? \"mixed\" : on}\n          onChange={(e) => {\n            if (checked === undefined) setOwn(e.target.checked);\n            onChange?.(e);\n            onCheckedChange?.(e.target.checked);\n          }}\n          className=\"peer absolute inset-0 m-0 size-full cursor-[inherit] appearance-none rounded-[inherit] outline-none\"\n          {...props}\n        />\n        {/* The box: a hairline that the fill grows over. */}\n        <span\n          aria-hidden=\"true\"\n          className={`pointer-events-none absolute inset-0 rounded-[inherit] bg-background shadow-[inset_0_0_0_1px_var(--border)] transition-[box-shadow,scale] duration-200 group-hover/cb:shadow-[inset_0_0_0_1px_color-mix(in_oklab,var(--foreground)_30%,var(--border))] group-active/cb:scale-[0.88] peer-focus-visible:shadow-[inset_0_0_0_1px_var(--ring),0_0_0_3px_color-mix(in_oklab,var(--ring)_20%,transparent)] motion-reduce:group-active/cb:scale-100`}\n        >\n          <span\n            className=\"absolute inset-0 rounded-[inherit] bg-primary\"\n            style={{ transform: on || mixed ? \"scale(1)\" : \"scale(0.4)\", opacity: on || mixed ? 1 : 0, transition: reduced ? \"none\" : `transform ${t(320, 0, EASE_THROW)}, opacity ${t(160)}` }}\n          />\n          <svg viewBox=\"0 0 16 16\" fill=\"none\" className=\"absolute inset-0 size-full text-primary-foreground\">\n            {/* The tick draws on the way in and retracts on the way out. */}\n            <path\n              d=\"M4 8.4 6.8 11 12 5.2\"\n              stroke=\"currentColor\"\n              strokeWidth=\"2\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              pathLength={1}\n              strokeDasharray={1}\n              style={{ strokeDashoffset: on ? 0 : 1, transition: reduced ? \"none\" : `stroke-dashoffset ${on ? t(300, 90) : t(160)}` }}\n            />\n            <path\n              d=\"M4.5 8h7\"\n              stroke=\"currentColor\"\n              strokeWidth=\"2\"\n              strokeLinecap=\"round\"\n              style={{ transform: mixed ? \"scaleX(1)\" : \"scaleX(0)\", transformOrigin: \"8px 8px\", transition: reduced ? \"none\" : `transform ${t(260, mixed ? 60 : 0)}` }}\n            />\n          </svg>\n        </span>\n      </span>\n      {(label || description) && (\n        <span className=\"flex min-w-0 flex-col gap-0.5\">\n          {label && <span className=\"text-[13.5px] font-medium leading-5 text-foreground\">{label}</span>}\n          {description && <span className=\"text-[12.5px] leading-snug text-muted-foreground\">{description}</span>}\n        </span>\n      )}\n    </label>\n  );\n}\n\nexport default Checkbox;\n"}],"css":{},"docs":"Docs and live preview: https://hairlineui.com/ui/checkbox. MIT licensed: https://hairlineui.com/license#components","meta":{"license":"MIT"}}