{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"checkbox","type":"registry:ui","title":"Checkbox","description":"Native checkbox whose tick draws itself with a spring, morphs to a dash when indeterminate, and groups under a select-all parent.","author":"GetUrUI <hello@geturui.dev>","dependencies":["motion"],"registryDependencies":[],"files":[{"path":"registry/geturui/ui/checkbox.tsx","type":"registry:ui","target":"components/ui/checkbox.tsx","content":"\"use client\";\n\nimport * as React from \"react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type CheckboxSize = \"sm\" | \"md\" | \"lg\";\n\nconst box: Record<CheckboxSize, string> = { sm: \"size-4 rounded-[5px]\", md: \"size-5 rounded-[6px]\", lg: \"size-6 rounded-[7px]\" };\nconst text: Record<CheckboxSize, string> = { sm: \"text-[13px] leading-4\", md: \"text-sm leading-5\", lg: \"text-[15px] leading-6\" };\n\nexport interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"size\" | \"onChange\" | \"type\"> {\n  /** 16 / 20 / 24 px box. @default \"md\" */\n  size?: CheckboxSize;\n  /** Shows a dash: some, not all, children are checked. */\n  indeterminate?: boolean;\n  label?: React.ReactNode;\n  /** Supporting text under the label. */\n  hint?: React.ReactNode;\n  /** Marks the box invalid; a string is announced as the error. */\n  error?: boolean | string;\n  onCheckedChange?: (checked: boolean) => void;\n  onChange?: React.ChangeEventHandler<HTMLInputElement>;\n  wrapperClassName?: string;\n}\n\n/** Native checkbox under a spring-drawn tick: the box pops on press, the check draws its stroke, indeterminate morphs to a dash. */\nexport const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(function Checkbox(\n  { size = \"md\", indeterminate, label, hint, error, checked, defaultChecked, onCheckedChange, onChange, disabled, className, wrapperClassName, id, ...props },\n  ref,\n) {\n  const auto = React.useId();\n  const inputId = id ?? `cb${auto.replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  const inner = React.useRef<HTMLInputElement>(null);\n  React.useImperativeHandle(ref, () => inner.current as HTMLInputElement);\n  const [own, setOwn] = React.useState(Boolean(defaultChecked));\n  const isOn = checked ?? own;\n  const reduce = useReducedMotion();\n  React.useEffect(() => {\n    if (inner.current) inner.current.indeterminate = Boolean(indeterminate);\n  }, [indeterminate]);\n  const invalid = Boolean(error);\n  const describedBy = hint || typeof error === \"string\" ? `${inputId}-desc` : undefined;\n  const filled = isOn || indeterminate;\n\n  const control = (\n    <span className={cn(\"relative inline-grid shrink-0 place-items-center\", box[size])}>\n      <input\n        ref={inner}\n        id={inputId}\n        type=\"checkbox\"\n        checked={isOn}\n        disabled={disabled}\n        aria-invalid={invalid || undefined}\n        aria-describedby={describedBy}\n        onChange={(e) => {\n          if (checked === undefined) setOwn(e.target.checked);\n          onCheckedChange?.(e.target.checked);\n          onChange?.(e);\n        }}\n        className=\"peer absolute inset-0 m-0 cursor-pointer appearance-none rounded-[inherit] opacity-0 disabled:cursor-not-allowed\"\n        {...props}\n      />\n      <motion.span\n        aria-hidden\n        tabIndex={-1}\n        animate={{ scale: 1 }}\n        whileTap={reduce ? undefined : { scale: 0.86 }}\n        className={cn(\n          \"pointer-events-none grid size-full place-items-center rounded-[inherit] border transition-[background-color,border-color,box-shadow] duration-150\",\n          \"peer-focus-visible:shadow-[var(--focus-ring)]\",\n          filled ? \"border-volt bg-volt text-on-volt\" : invalid ? \"border-danger bg-surface-sunken\" : \"border-border-strong bg-surface-sunken peer-hover:border-ink-subtle\",\n          disabled && \"opacity-45\",\n          className,\n        )}\n      >\n        <svg viewBox=\"0 0 16 16\" className=\"size-[78%]\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.4} strokeLinecap=\"round\" strokeLinejoin=\"round\">\n          <motion.path\n            d={indeterminate ? \"M4 8h8\" : \"M3.5 8.5l3 3 6-7\"}\n            initial={false}\n            animate={{ pathLength: filled ? 1 : 0, opacity: filled ? 1 : 0 }}\n            transition={reduce ? { duration: 0 } : { type: \"spring\", stiffness: 420, damping: 30 }}\n          />\n        </svg>\n      </motion.span>\n    </span>\n  );\n\n  if (!label && !hint) return <span className={wrapperClassName}>{control}</span>;\n  return (\n    <div className={cn(\"flex items-start gap-2.5\", disabled && \"cursor-not-allowed\", wrapperClassName)}>\n      <span className={cn(\"flex items-center\", size === \"sm\" ? \"h-4\" : size === \"md\" ? \"h-5\" : \"h-6\")}>{control}</span>\n      <span className=\"grid gap-0.5\">\n        {label && (\n          <label htmlFor={inputId} className={cn(\"cursor-pointer font-medium text-ink\", text[size], disabled && \"cursor-not-allowed opacity-45\")}>\n            {label}\n          </label>\n        )}\n        {(hint || typeof error === \"string\") && (\n          <span id={describedBy} className={cn(\"text-[13px] leading-5\", typeof error === \"string\" ? \"text-danger\" : \"text-ink-muted\")}>\n            {typeof error === \"string\" ? error : hint}\n          </span>\n        )}\n      </span>\n    </div>\n  );\n});\n\nexport interface CheckboxGroupOption {\n  value: string;\n  label: React.ReactNode;\n  hint?: React.ReactNode;\n  disabled?: boolean;\n}\n\nexport interface CheckboxGroupProps {\n  /** Group heading, rendered as the fieldset legend. */\n  label: React.ReactNode;\n  options: CheckboxGroupOption[];\n  value?: string[];\n  defaultValue?: string[];\n  onValueChange?: (value: string[]) => void;\n  size?: CheckboxSize;\n  /** Adds a parent \"Select all\" box that turns indeterminate on partial selection. */\n  selectAll?: React.ReactNode;\n  /** Lay options out in a row on wide screens. */\n  orientation?: \"vertical\" | \"horizontal\";\n  hint?: React.ReactNode;\n  error?: string;\n  className?: string;\n}\n\n/** Fieldset of checkboxes with an optional select-all parent that tracks partial selection. */\nexport function CheckboxGroup({\n  label,\n  options,\n  value,\n  defaultValue = [],\n  onValueChange,\n  size = \"md\",\n  selectAll,\n  orientation = \"vertical\",\n  hint,\n  error,\n  className,\n}: CheckboxGroupProps) {\n  const [own, setOwn] = React.useState<string[]>(defaultValue);\n  const selected = value ?? own;\n  const set = (next: string[]) => {\n    if (value === undefined) setOwn(next);\n    onValueChange?.(next);\n  };\n  const enabled = options.filter((o) => !o.disabled).map((o) => o.value);\n  const all = enabled.length > 0 && enabled.every((v) => selected.includes(v));\n  const some = !all && enabled.some((v) => selected.includes(v));\n  return (\n    <fieldset className={cn(\"m-0 grid min-w-0 gap-3 border-0 p-0\", className)} aria-invalid={error ? true : undefined}>\n      <legend className=\"mb-3 p-0 text-[13px] font-medium leading-[18px] text-ink\">{label}</legend>\n      {selectAll && (\n        <Checkbox\n          size={size}\n          label={selectAll}\n          checked={all}\n          indeterminate={some}\n          onCheckedChange={(on) => set(on ? Array.from(new Set([...selected, ...enabled])) : selected.filter((v) => !enabled.includes(v)))}\n          wrapperClassName=\"border-b border-border pb-3\"\n        />\n      )}\n      <div className={cn(\"grid gap-3\", orientation === \"horizontal\" && \"sm:flex sm:flex-wrap sm:gap-x-6\")}>\n        {options.map((o) => (\n          <Checkbox\n            key={o.value}\n            size={size}\n            label={o.label}\n            hint={o.hint}\n            disabled={o.disabled}\n            error={Boolean(error)}\n            checked={selected.includes(o.value)}\n            onCheckedChange={(on) => set(on ? [...selected, o.value] : selected.filter((v) => v !== o.value))}\n          />\n        ))}\n      </div>\n      {(error || hint) && <p className={cn(\"m-0 text-xs font-medium leading-4\", error ? \"text-danger\" : \"text-ink-subtle\")}>{error || hint}</p>}\n    </fieldset>\n  );\n}\n"},{"path":"LICENSE-geturui.md","type":"registry:file","target":"LICENSE-geturui.md","content":"MIT License\n\nCopyright (c) 2026 geturui.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"}],"categories":["components","checkbox","form","indeterminate","select all","fieldset"],"meta":{"tier":"free","license":"MIT","docs":"https://geturui.com/docs/checkbox"}}