{
  "schemaVersion": 1,
  "target": "react",
  "name": "input",
  "version": "0.1.0",
  "category": "core",
  "title": "Input",
  "description": "A text field with a label, helper text, error and busy states, wired up for screen readers.",
  "tags": [
    "form",
    "field",
    "text",
    "validation"
  ],
  "requires": {
    "projectTypes": [
      "next",
      "vite-react"
    ],
    "styling": "tailwind-v4"
  },
  "npmDependencies": {},
  "registryDependencies": [],
  "docsPath": "/components/input",
  "props": [
    {
      "name": "label",
      "type": "ReactNode",
      "description": "Visible label, linked to the input. Use aria-label when the label is shown elsewhere."
    },
    {
      "name": "description",
      "type": "ReactNode",
      "description": "Helper text below the field, linked with aria-describedby."
    },
    {
      "name": "error",
      "type": "ReactNode",
      "description": "Error message. Sets aria-invalid and replaces the description."
    },
    {
      "name": "busy",
      "type": "boolean",
      "default": "false",
      "description": "Shows a spinner and sets aria-busy, for example while checking a value."
    },
    {
      "name": "containerClassName",
      "type": "string",
      "description": "Classes for the wrapper. className styles the input itself."
    },
    {
      "name": "...props",
      "type": "ComponentProps<\"input\">",
      "description": "Any native input attribute, including disabled, type, and value."
    }
  ],
  "accessibility": [
    "The label is a native <label> bound to the input with an id generated by useId.",
    "Helper text and errors are linked with aria-describedby; errors also set aria-invalid.",
    "Busy fields set aria-busy; the spinner is decorative and stops rotating with reduced motion.",
    "Focus shows a ring that does not rely on color alone (border and ring)."
  ],
  "states": [],
  "files": [
    {
      "path": "ui/input.tsx",
      "content": "\"use client\";\n\nimport { type ComponentProps, type ReactNode, useId } from \"react\";\n\nexport interface InputProps extends Omit<ComponentProps<\"input\">, \"size\"> {\n  /** Visible label. Pass `aria-label` instead when the label is shown elsewhere. */\n  label?: ReactNode;\n  /** Helper text below the field. */\n  description?: ReactNode;\n  /** Error message. Marks the field invalid and replaces the description. */\n  error?: ReactNode;\n  /** Shows a spinner in the field, for example while a value is being checked. */\n  busy?: boolean;\n  /** Classes for the wrapping element. `className` styles the input itself. */\n  containerClassName?: string;\n}\n\nexport function Input({\n  label,\n  description,\n  error,\n  busy = false,\n  id,\n  className,\n  containerClassName,\n  disabled,\n  \"aria-describedby\": describedBy,\n  ...props\n}: InputProps) {\n  const generatedId = useId();\n  const inputId = id ?? generatedId;\n  const messageId = `${inputId}-message`;\n  const invalid = error != null && error !== false;\n  const message = invalid ? error : description;\n\n  return (\n    <div className={[\"grid gap-1.5\", containerClassName].filter(Boolean).join(\" \")}>\n      {label ? (\n        <label htmlFor={inputId} className=\"text-sm font-medium text-zinc-900 dark:text-zinc-100\">\n          {label}\n        </label>\n      ) : null}\n      <div className=\"relative\">\n        <input\n          id={inputId}\n          disabled={disabled}\n          aria-invalid={invalid || undefined}\n          aria-busy={busy || undefined}\n          aria-describedby={[message ? messageId : null, describedBy].filter(Boolean).join(\" \") || undefined}\n          className={[\n            \"h-9 w-full rounded-lg border bg-white px-3 text-sm text-zinc-900 shadow-sm placeholder:text-zinc-400\",\n            \"transition-[border-color,box-shadow] duration-150 motion-reduce:transition-none\",\n            \"focus-visible:border-sky-500 focus-visible:ring-3 focus-visible:ring-sky-500/20 focus-visible:outline-none\",\n            \"disabled:cursor-not-allowed disabled:bg-zinc-50 disabled:opacity-60\",\n            \"dark:bg-zinc-950 dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:disabled:bg-zinc-900\",\n            invalid\n              ? \"border-red-400 focus-visible:border-red-500 focus-visible:ring-red-500/20 dark:border-red-500/60\"\n              : \"border-zinc-300 dark:border-zinc-700\",\n            busy ? \"pr-9\" : \"\",\n            className,\n          ]\n            .filter(Boolean)\n            .join(\" \")}\n          {...props}\n        />\n        {busy ? (\n          <svg\n            aria-hidden=\"true\"\n            viewBox=\"0 0 16 16\"\n            fill=\"none\"\n            className=\"pointer-events-none absolute top-1/2 right-3 size-4 -translate-y-1/2 text-zinc-400 motion-safe:animate-spin\"\n          >\n            <circle cx=\"8\" cy=\"8\" r=\"6\" stroke=\"currentColor\" strokeOpacity=\"0.3\" strokeWidth=\"2\" />\n            <path d=\"M14 8a6 6 0 0 0-6-6\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" />\n          </svg>\n        ) : null}\n      </div>\n      {message ? (\n        <p id={messageId} className={invalid ? \"text-sm text-red-600 dark:text-red-400\" : \"text-sm text-zinc-500 dark:text-zinc-400\"}>\n          {message}\n        </p>\n      ) : null}\n    </div>\n  );\n}\n"
    }
  ],
  "example": {
    "content": "import { Input } from \"@/components/seamless/ui/input\";\n\nexport default function InputExample() {\n  return (\n    <form className=\"grid w-full max-w-sm gap-4\">\n      <Input label=\"Workspace name\" placeholder=\"Acme\" description=\"Shown to everyone on your team.\" />\n      <Input label=\"Email\" type=\"email\" defaultValue=\"ada@\" error=\"Enter a complete email address.\" />\n      <Input label=\"Subdomain\" defaultValue=\"acme\" busy description=\"Checking availability…\" />\n      <Input label=\"Plan\" defaultValue=\"Pro\" disabled />\n    </form>\n  );\n}\n"
  }
}
