{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tags-input",
  "type": "registry:ui",
  "title": "Tags input",
  "description": "React tags input that adds a chip on Enter or comma, skips duplicates, removes the last tag on Backspace and gives each chip a labelled remove button.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/tags-input.tsx",
      "type": "registry:ui",
      "target": "components/ui/tags-input.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\nimport { XIcon } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\n\ntype TagsInputProps = {\n  value?: string[]\n  defaultValue?: string[]\n  onChange?: (tags: string[]) => void\n  placeholder?: string\n  disabled?: boolean\n  className?: string\n  \"aria-label\"?: string\n  \"aria-invalid\"?: boolean\n}\n\nfunction TagsInput({ value, defaultValue = [], onChange, placeholder = \"Add tag\", disabled, className, ...a11y }: TagsInputProps) {\n  const [internal, setInternal] = React.useState(defaultValue)\n  const tags = value ?? internal\n  const [draft, setDraft] = React.useState(\"\")\n  const setTags = (next: string[]) => {\n    if (value === undefined) setInternal(next)\n    onChange?.(next)\n  }\n  const commit = () => {\n    const t = draft.trim()\n    if (!t || tags.includes(t)) { setDraft(\"\"); return }\n    setTags([...tags, t])\n    setDraft(\"\")\n  }\n  return (\n    <div\n      data-slot=\"tags-input\"\n      className={cn(\n        \"flex min-h-9 w-full flex-wrap items-center gap-1.5 rounded-lg border border-border bg-surface px-2 py-1.5\",\n        \"shadow-highlight\",\n        \"focus-within:border-accent focus-within:ring-2 focus-within:ring-accent focus-within:ring-offset-2 focus-within:ring-offset-bg\",\n        \"aria-invalid:border-danger\",\n        disabled && \"pointer-events-none bg-sunken\",\n        className\n      )}\n      aria-invalid={a11y[\"aria-invalid\"]}\n    >\n      {tags.map((tag) => (\n        <span key={tag} className=\"inline-flex h-6 items-center gap-1 rounded-md border border-border bg-sunken px-2 text-xs font-medium text-fg\">\n          {tag}\n          <button\n            type=\"button\"\n            aria-label={`Remove ${tag}`}\n            disabled={disabled}\n            className=\"text-fg-muted hover:text-fg\"\n            onClick={() => setTags(tags.filter((x) => x !== tag))}\n          >\n            <XIcon className=\"size-3\" />\n          </button>\n        </span>\n      ))}\n      <input\n        aria-label={a11y[\"aria-label\"] ?? \"Tag input\"}\n        disabled={disabled}\n        value={draft}\n        placeholder={tags.length ? undefined : placeholder}\n        className=\"min-w-24 flex-1 bg-transparent text-sm text-fg outline-none placeholder:text-fg-muted\"\n        onChange={(e) => setDraft(e.target.value)}\n        onKeyDown={(e) => {\n          if (e.key === \"Enter\" || e.key === \",\") { e.preventDefault(); commit() }\n          if (e.key === \"Backspace\" && !draft && tags.length) setTags(tags.slice(0, -1))\n        }}\n        onBlur={commit}\n      />\n    </div>\n  )\n}\nexport { TagsInput }\n"
    }
  ]
}
