{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"field","type":"registry:ui","title":"Field","description":"Label, control and hint line with ids wired for screen readers; errors shake the control once and blur in under it.","author":"GetUrUI <hello@geturui.dev>","dependencies":["motion","lucide-react"],"registryDependencies":[],"files":[{"path":"registry/geturui/ui/field.tsx","type":"registry:ui","target":"components/ui/field.tsx","content":"\"use client\";\n\nimport * as React from \"react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { AlertCircle } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type FieldSize = \"sm\" | \"md\" | \"lg\";\n\nconst wellSizes: Record<FieldSize, string> = {\n  sm: \"min-h-8 px-2.5 text-[13px] gap-1.5 rounded-[9px]\",\n  md: \"min-h-10 px-3 text-sm gap-2 rounded-[10px]\",\n  lg: \"min-h-12 px-3.5 text-[15px] gap-2.5 rounded-xs\",\n};\n\n/**\n * Class string for the sunken \"well\" every text-like control sits in (Input, Textarea, Select trigger).\n * Heights match Button: sm 32 · md 40 · lg 48.\n */\nexport function fieldWellClasses({\n  size = \"md\",\n  invalid,\n  disabled,\n  className,\n}: {\n  size?: FieldSize;\n  invalid?: boolean;\n  disabled?: boolean;\n  className?: string;\n}) {\n  return cn(\n    \"flex w-full items-center border bg-surface-sunken text-ink-subtle transition-[border-color,box-shadow,background-color] duration-150\",\n    \"focus-within:border-volt-ink focus-within:shadow-[0_0_0_3px_var(--volt-soft)]\",\n    invalid ? \"border-danger focus-within:border-danger focus-within:shadow-[0_0_0_3px_var(--danger-soft)]\" : \"border-border-strong hover:border-ink-subtle\",\n    disabled && \"pointer-events-none opacity-45\",\n    wellSizes[size],\n    className,\n  );\n}\n\nexport interface FieldIds {\n  /** id for the control itself. */\n  id: string;\n  /** id of the label element. */\n  labelId: string;\n  /** id of the hint/error line, set only when one is shown. */\n  describedBy?: string;\n  invalid: boolean;\n}\n\nexport interface FieldProps {\n  label?: React.ReactNode;\n  /** Helper text under the control. Replaced by `error` when both are set. */\n  hint?: React.ReactNode;\n  /** Error message. Any truthy value marks the control invalid; `true` shows no message. */\n  error?: React.ReactNode;\n  /** Adds a required marker after the label. */\n  required?: boolean;\n  /** Right-aligned slot on the label row: \"Optional\", a counter, a link. */\n  labelAside?: React.ReactNode;\n  /** Use an existing id for the control. */\n  id?: string;\n  disabled?: boolean;\n  /** Shake once when the field turns invalid. @default true */\n  shakeOnError?: boolean;\n  className?: string;\n  /** The control. A function receives the ids and aria wiring to spread on it. */\n  children: React.ReactNode | ((ids: FieldIds) => React.ReactNode);\n}\n\n/** Label, control and hint/error line with ids wired for screen readers; errors shake once and swap in with a blur. */\nexport function Field({ label, hint, error, required, labelAside, id, disabled, shakeOnError = true, className, children }: FieldProps) {\n  const auto = React.useId();\n  const controlId = id ?? `f${auto.replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  const invalid = Boolean(error);\n  const message = typeof error === \"boolean\" ? hint : error || hint;\n  const ids: FieldIds = {\n    id: controlId,\n    labelId: `${controlId}-label`,\n    describedBy: message ? `${controlId}-msg` : undefined,\n    invalid,\n  };\n  const reduce = useReducedMotion();\n  return (\n    <div className={cn(\"grid gap-1.5\", disabled && \"cursor-not-allowed\", className)}>\n      {(label || labelAside) && (\n        <div className=\"flex items-baseline justify-between gap-3\">\n          {label && (\n            <label id={ids.labelId} htmlFor={controlId} className=\"text-[13px] font-medium leading-[18px] text-ink\">\n              {label}\n              {required && (\n                <span aria-hidden className=\"ml-0.5 text-volt-ink\">\n                  *\n                </span>\n              )}\n            </label>\n          )}\n          {labelAside && <span className=\"text-xs leading-4 text-ink-subtle\">{labelAside}</span>}\n        </div>\n      )}\n      <motion.div\n        animate={invalid && shakeOnError && !reduce ? { x: [0, -4, 4, -2, 2, 0] } : { x: 0 }}\n        transition={{ duration: 0.36 }}\n      >\n        {typeof children === \"function\" ? children(ids) : children}\n      </motion.div>\n      <AnimatePresence initial={false} mode=\"popLayout\">\n        {message && (\n          <motion.p\n            key={invalid && typeof error !== \"boolean\" ? \"error\" : \"hint\"}\n            id={ids.describedBy}\n            initial={reduce ? { opacity: 0 } : { opacity: 0, filter: \"blur(4px)\", y: -2 }}\n            animate={{ opacity: 1, filter: \"blur(0px)\", y: 0 }}\n            exit={{ opacity: 0, filter: \"blur(4px)\" }}\n            transition={{ duration: 0.18 }}\n            className={cn(\n              \"flex items-center gap-1 text-xs font-medium leading-4\",\n              invalid && typeof error !== \"boolean\" ? \"text-danger\" : \"text-ink-subtle\",\n            )}\n          >\n            {invalid && typeof error !== \"boolean\" && <AlertCircle aria-hidden className=\"size-3.5 shrink-0\" />}\n            {message}\n          </motion.p>\n        )}\n      </AnimatePresence>\n    </div>\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","form","label","hint","error","validation","input"],"meta":{"tier":"free","license":"MIT","docs":"https://geturui.com/docs/field"}}