{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "checkbox",
  "type": "registry:ui",
  "title": "Checkbox",
  "description": "A single checkbox, with the mixed state a parent selection needs.",
  "categories": [
    "forms"
  ],
  "registryDependencies": [
    "https://whiskeyjack.net/r/merge-refs.json",
    "https://whiskeyjack.net/r/utils.json"
  ],
  "files": [
    {
      "path": "components/ui/checkbox.tsx",
      "type": "registry:ui",
      "target": "components/ui/checkbox.tsx",
      "content": "import * as React from 'react'\nimport { cn } from '@/lib/utils'\nimport { useMergedRefs } from '@/lib/merge-refs'\n\nexport interface CheckboxProps\n  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'size'> {\n  /**\n   * Rendered beside the box inside a wrapping `<label>`, so the text is part\n   * of the hit area. Without it the caller supplies `aria-label`.\n   */\n  label?: React.ReactNode\n  /**\n   * The mixed state, for a parent box governing a partial selection. It is a\n   * DOM property with no HTML attribute, so it is applied through the ref and\n   * cannot be expressed in markup alone. Checked wins if both are set, which\n   * mirrors the platform.\n   */\n  indeterminate?: boolean\n}\n\nconst BOX = cn(\n  // Same field vocabulary as Input and SearchField -- solid border, no fill of\n  // its own, deboss recess -- at the smallest size the border radius still\n  // reads as the family's `rounded-lg` rather than a circle.\n  'peer relative h-[18px] w-[18px] shrink-0 appearance-none rounded-[5px]',\n  'border border-[var(--color-border-light)] dark:border-[var(--color-border-dark)]',\n  'bg-transparent shadow-deboss',\n  'hover:border-[var(--color-warm-300)] dark:hover:border-[var(--color-neutral-700)]',\n  'transition-colors wj-focus-ring',\n  // Both filled states take the accent, so a mixed parent and a checked child\n  // read as the same control in two positions.\n  'checked:border-[var(--color-accent-500)] checked:bg-[var(--color-accent-500)]',\n  'indeterminate:border-[var(--color-accent-500)] indeterminate:bg-[var(--color-accent-500)]',\n  'checked:shadow-none indeterminate:shadow-none',\n  'disabled:cursor-not-allowed disabled:opacity-50',\n  'disabled:hover:border-[var(--color-border-light)] dark:disabled:hover:border-[var(--color-border-dark)]',\n)\n\n// The mark sits over the box rather than inside it, because an appearance-none\n// input cannot hold children. Inline SVG instead of an icon library: the DS\n// bundles none.\nconst MARK = cn(\n  'pointer-events-none absolute left-0 top-0 h-[18px] w-[18px]',\n  'text-[var(--color-accent-foreground-icon)] opacity-0',\n  'peer-checked:opacity-100 peer-indeterminate:opacity-0',\n)\n\nconst DASH = cn(\n  'pointer-events-none absolute left-0 top-0 h-[18px] w-[18px]',\n  'text-[var(--color-accent-foreground-icon)] opacity-0',\n  'peer-indeterminate:opacity-100',\n)\n\n/**\n * A single checkbox: a real `<input type=\"checkbox\">` with the DS field shape\n * drawn on it.\n *\n * `CheckboxGroup` is a tile grid answering \"which of these\", and it owns its\n * own boxes. This is the one you reach for when a checkbox stands alone -- a\n * selectable row in a table, a tile's selection affordance, a lone consent\n * box -- and it is the only DS control that carries a mixed state.\n *\n * The box is 18px, which is smaller than the 44px touch floor on purpose: a\n * dense grid of them would be unusable at 44px, and a labelled row already\n * clears it. **A bare box in a touch context needs its hit area from the\n * caller** (`touch-target` on the cell, gated on `useInputType()`), the way a\n * bulk-selection grid does.\n *\n * ```tsx\n * <Checkbox\n *   label={t('settings.reduceMotion')}\n *   checked={reduceMotion}\n *   onChange={(e) => setReduceMotion(e.target.checked)}\n * />\n *\n * <Checkbox\n *   aria-label={t('gallery.selectAll')}\n *   checked={all}\n *   indeterminate={some && !all}\n *   onChange={toggleAll}\n * />\n * ```\n */\nconst Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(\n  ({ className, label, indeterminate = false, disabled, ...props }, ref) => {\n    const innerRef = React.useRef<HTMLInputElement>(null)\n    const mergedRef = useMergedRefs(innerRef, ref)\n\n    React.useEffect(() => {\n      const el = innerRef.current\n      if (el) el.indeterminate = indeterminate\n    }, [indeterminate, props.checked, props.defaultChecked])\n\n    const box = (\n      <span className=\"relative inline-flex shrink-0\">\n        <input type=\"checkbox\" ref={mergedRef} disabled={disabled} className={cn(BOX, className)} {...props} />\n        <svg className={MARK} viewBox=\"0 0 18 18\" fill=\"none\" aria-hidden=\"true\">\n          <path\n            d=\"M4.5 9.5l3 3 6-6\"\n            stroke=\"currentColor\"\n            strokeWidth=\"2\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n        </svg>\n        <svg className={DASH} viewBox=\"0 0 18 18\" fill=\"none\" aria-hidden=\"true\">\n          <path d=\"M5 9h8\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" />\n        </svg>\n      </span>\n    )\n\n    if (label === undefined) return box\n\n    return (\n      <label\n        className={cn(\n          'inline-flex items-center gap-2 text-sm',\n          'text-[var(--color-text-primary-light)] dark:text-[var(--color-text-primary-dark)]',\n          disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',\n        )}\n      >\n        {box}\n        <span>{label}</span>\n      </label>\n    )\n  },\n)\nCheckbox.displayName = 'Checkbox'\n\nexport { Checkbox }\n"
    }
  ],
  "docs": "A real input type=checkbox wearing the DS field shape. CheckboxGroup is a tile grid answering \"which of these\" and owns its own boxes; this is the one for a checkbox that stands alone -- a selectable row, a tile's selection affordance, a lone consent box -- and it is the only DS control carrying a mixed state, via `indeterminate` (a DOM property with no HTML attribute, so it is applied through the ref). Pass `label` to get a wrapping label element with the text inside the hit area, or supply your own aria-label. The box is 18px, below the 44px touch floor on purpose, because a dense grid of them would be unusable at 44px: a bare box in a touch context needs its hit area from the caller, gated on useInputType.",
  "meta": {
    "group": "forms",
    "related": [
      "checkbox-group",
      "toggle",
      "toggle-group"
    ],
    "exports": [
      "Checkbox"
    ],
    "siteSlug": "checkbox"
  }
}
