{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "tags-input",
  "title": "Tags Input",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/tags-input/TagsInput.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface TagsInputProps extends HTMLAttributes<HTMLDivElement> {\n    /** Controlled tag list. Bind with `bind:value`. */\n    value?: string[]\n    /** Initial tags for uncontrolled usage. */\n    defaultValue?: string[]\n    disabled?: boolean\n    /** Placeholder for the inner input (React parity; the input prop still wins when set). */\n    placeholder?: string\n    /** Split pasted text on whitespace and add each token. */\n    addOnPaste?: boolean\n    /** Also commit the input when it blurs. */\n    addOnBlur?: boolean\n    /** Keys that commit the typed value into a tag. Defaults to Enter + comma (React parity). */\n    addOnKeys?: string[]\n    /** Commit the input when this character is typed (in addition to Enter).\n     * @deprecated Use `addOnKeys`. Kept as an alias — both stay functional. */\n    delimiter?: string\n    /** Reject duplicate tags (case-sensitive). Defaults to true (React parity). */\n    unique?: boolean\n    /** Maximum number of tags. */\n    max?: number\n    ref?: HTMLDivElement | null\n    onValueChange?: (value: string[]) => void\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { setTagsInputContext } from './context'\n\n  let {\n    class: className,\n    defaultValue = [],\n    value = $bindable(defaultValue),\n    disabled = false,\n    placeholder,\n    addOnPaste = false,\n    addOnBlur = false,\n    addOnKeys,\n    delimiter,\n    unique = true,\n    max,\n    children,\n    ref = $bindable(null),\n    onValueChange,\n    ...restProps\n  }: TagsInputProps = $props()\n\n  const commitKeys = $derived.by((): string[] => {\n    if (addOnKeys) return addOnKeys\n    if (delimiter) return [...new Set(['Enter', delimiter])]\n    return ['Enter', ',']\n  })\n\n  function commit(next: string[]) {\n    value = next\n    onValueChange?.(next)\n  }\n\n  function addValue(raw: string) {\n    if (disabled) return\n    const trimmed = raw.trim()\n    if (!trimmed) return\n    const current = value ?? []\n    if (unique && current.includes(trimmed)) return\n    if (max !== undefined && current.length >= max) return\n    commit([...current, trimmed])\n  }\n\n  function removeValue(target: string) {\n    if (disabled) return\n    commit((value ?? []).filter((v) => v !== target))\n  }\n\n  function removeLast() {\n    if (disabled) return\n    const current = value ?? []\n    if (current.length === 0) return\n    commit(current.slice(0, -1))\n  }\n\n  setTagsInputContext({\n    values: () => value ?? [],\n    disabled: () => disabled,\n    addValue,\n    removeValue,\n    removeLast,\n    addOnPaste: () => addOnPaste,\n    addOnBlur: () => addOnBlur,\n    delimiter: () => delimiter,\n    commitKeys: () => commitKeys,\n    placeholder: () => placeholder,\n  })\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge=\"\"\n  data-slot=\"tags-input\"\n  data-disabled={disabled || undefined}\n  class={cn(\n    'border-input bg-background flex flex-wrap items-center gap-2 rounded-md border px-2 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none',\n    'focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px]',\n    'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',\n    disabled && 'cursor-not-allowed opacity-60',\n    className,\n  )}\n  {...restProps}\n>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/tags-input/TagsInput.svelte"
    },
    {
      "path": "packages/registry-svelte/components/tags-input/TagsInputInput.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLInputAttributes } from 'svelte/elements'\n\n  export interface TagsInputInputProps extends HTMLInputAttributes {\n    ref?: HTMLInputElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import type { ClipboardEventHandler, FocusEventHandler, KeyboardEventHandler } from 'svelte/elements'\n  import { cn } from '$lib/utils'\n  import { getTagsInputContext } from './context'\n\n  let {\n    class: className,\n    disabled,\n    placeholder,\n    ref = $bindable(null),\n    onkeydown,\n    onpaste,\n    onblur,\n    ...restProps\n  }: TagsInputInputProps = $props()\n\n  const ctx = getTagsInputContext()\n  const isDisabled = $derived(disabled ?? ctx?.disabled() ?? false)\n  const effectivePlaceholder = $derived(placeholder ?? ctx?.placeholder())\n\n  const handleKeydown: KeyboardEventHandler<HTMLInputElement> = (event) => {\n    onkeydown?.(event)\n    if (event.defaultPrevented || isDisabled) return\n    const input = event.currentTarget\n    const commitKeys = ctx?.commitKeys() ?? ['Enter', ',']\n    if (commitKeys.includes(event.key)) {\n      event.preventDefault()\n      if (input.value) {\n        ctx?.addValue(input.value)\n        input.value = ''\n      }\n    } else if (event.key === 'Backspace' && input.value === '') {\n      event.preventDefault()\n      ctx?.removeLast()\n    }\n  }\n\n  const handlePaste: ClipboardEventHandler<HTMLInputElement> = (event) => {\n    onpaste?.(event)\n    if (event.defaultPrevented || isDisabled || !ctx?.addOnPaste()) return\n    const text = event.clipboardData?.getData('text')\n    if (!text) return\n    event.preventDefault()\n    const delimiter = ctx.delimiter()\n    const tokens = delimiter ? text.split(delimiter) : text.split(/\\s+/)\n    for (const token of tokens) ctx.addValue(token)\n    event.currentTarget.value = ''\n  }\n\n  const handleBlur: FocusEventHandler<HTMLInputElement> = (event) => {\n    onblur?.(event)\n    if (event.defaultPrevented || isDisabled || !ctx?.addOnBlur()) return\n    if (event.currentTarget.value) {\n      ctx.addValue(event.currentTarget.value)\n      event.currentTarget.value = ''\n    }\n  }\n</script>\n\n<input\n  bind:this={ref}\n  data-uipkge=\"\"\n  data-slot=\"tags-input-input\"\n  disabled={isDisabled}\n  aria-label=\"Add a tag\"\n  placeholder={effectivePlaceholder}\n  class={cn('min-h-5 flex-1 bg-transparent px-1 text-sm focus:outline-none', className)}\n  onkeydown={handleKeydown}\n  onpaste={handlePaste}\n  onblur={handleBlur}\n  {...restProps}\n/>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/tags-input/TagsInputInput.svelte"
    },
    {
      "path": "packages/registry-svelte/components/tags-input/TagsInputItem.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface TagsInputItemProps extends HTMLAttributes<HTMLDivElement> {\n    value: string\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { setTagsInputItem } from './context'\n\n  let { class: className, value, children, ref = $bindable(null), ...restProps }: TagsInputItemProps = $props()\n\n  setTagsInputItem(() => value)\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge=\"\"\n  data-slot=\"tags-input-item\"\n  data-value={value}\n  class={cn(\n    'bg-secondary data-[state=active]:ring-ring ring-offset-background flex h-5 items-center rounded-md data-[state=active]:ring-2 data-[state=active]:ring-offset-2',\n    className,\n  )}\n  {...restProps}\n>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/tags-input/TagsInputItem.svelte"
    },
    {
      "path": "packages/registry-svelte/components/tags-input/TagsInputItemDelete.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLButtonAttributes } from 'svelte/elements'\n\n  export interface TagsInputItemDeleteProps extends HTMLButtonAttributes {\n    ref?: HTMLButtonElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { X } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n  import { getTagsInputContext, getTagsInputItem } from './context'\n\n  let { class: className, children, ref = $bindable(null), disabled, onclick, ...restProps }: TagsInputItemDeleteProps =\n    $props()\n\n  const ctx = getTagsInputContext()\n  const getItem = getTagsInputItem()\n  const itemValue = $derived(getItem?.() ?? null)\n  const isDisabled = $derived(disabled ?? ctx?.disabled() ?? false)\n</script>\n\n<button\n  bind:this={ref}\n  type=\"button\"\n  data-uipkge=\"\"\n  data-slot=\"tags-input-item-delete\"\n  aria-label={itemValue ? `Remove ${itemValue}` : 'Remove tag'}\n  disabled={isDisabled}\n  class={cn(\n    'hover:text-foreground focus-visible:ring-ring mr-1 flex rounded bg-transparent focus-visible:ring-2 focus-visible:outline-none',\n    className,\n  )}\n  onclick={(event) => {\n    onclick?.(event)\n    if (!event.defaultPrevented && itemValue) ctx?.removeValue(itemValue)\n  }}\n  {...restProps}\n>\n  {#if children}{@render children()}{:else}<X class=\"h-4 w-4\" aria-hidden=\"true\" />{/if}\n</button>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/tags-input/TagsInputItemDelete.svelte"
    },
    {
      "path": "packages/registry-svelte/components/tags-input/TagsInputItemText.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface TagsInputItemTextProps extends HTMLAttributes<HTMLSpanElement> {\n    ref?: HTMLSpanElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { getTagsInputItem } from './context'\n\n  let { class: className, children, ref = $bindable(null), ...restProps }: TagsInputItemTextProps = $props()\n\n  // Renders the enclosing item's value when no explicit children are given\n  // (mirrors reka-ui TagsInputItemText default content).\n  const getItem = getTagsInputItem()\n  const itemValue = $derived(getItem?.() ?? null)\n</script>\n\n<span\n  bind:this={ref}\n  data-uipkge=\"\"\n  data-slot=\"tags-input-item-text\"\n  class={cn('rounded bg-transparent px-2 py-0.5 text-sm', className)}\n  {...restProps}\n>{#if children}{@render children()}{:else}{itemValue}{/if}</span\n>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/tags-input/TagsInputItemText.svelte"
    },
    {
      "path": "packages/registry-svelte/components/tags-input/context.ts",
      "content": "import { getContext, setContext } from 'svelte'\n\n/**\n * Tags-input state shared from <TagsInput> to its descendants. Exposed as\n * functions so reads inside child templates/`$derived` track the root's\n * runes state.\n */\nexport interface TagsInputContextValue {\n  values: () => string[]\n  disabled: () => boolean\n  addValue: (raw: string) => void\n  removeValue: (value: string) => void\n  removeLast: () => void\n  addOnPaste: () => boolean\n  addOnBlur: () => boolean\n  delimiter: () => string | undefined\n  /** Resolved commit keys (React `addOnKeys` parity; `delimiter` folds in as an alias). */\n  commitKeys: () => string[]\n  /** Root-level placeholder (React parity); the input falls back to it. */\n  placeholder: () => string | undefined\n}\n\nconst KEY = Symbol.for('uipkge:tags-input')\n\nexport function setTagsInputContext(value: TagsInputContextValue): TagsInputContextValue {\n  setContext(KEY, value)\n  return value\n}\n\nexport function getTagsInputContext(): TagsInputContextValue | null {\n  return getContext<TagsInputContextValue | null>(KEY) ?? null\n}\n\nconst ITEM_KEY = Symbol.for('uipkge:tags-input-item')\n\nexport function setTagsInputItem(getter: () => string): void {\n  setContext(ITEM_KEY, getter)\n}\n\nexport function getTagsInputItem(): (() => string) | null {\n  return getContext<(() => string) | null>(ITEM_KEY) ?? null\n}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/tags-input/context.ts"
    },
    {
      "path": "packages/registry-svelte/components/tags-input/index.ts",
      "content": "export { default as TagsInput, type TagsInputProps } from './TagsInput.svelte'\nexport { default as TagsInputInput, type TagsInputInputProps } from './TagsInputInput.svelte'\nexport { default as TagsInputItem, type TagsInputItemProps } from './TagsInputItem.svelte'\nexport { default as TagsInputItemDelete, type TagsInputItemDeleteProps } from './TagsInputItemDelete.svelte'\nexport { default as TagsInputItemText, type TagsInputItemTextProps } from './TagsInputItemText.svelte'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/tags-input/index.ts"
    }
  ],
  "dependencies": [
    "@lucide/svelte"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Multi-tag input — type a value, hit Enter, get a Chip. Backspace removes the last tag. Use for email recipient lists, tag sets, and free-form keyword inputs.",
  "categories": [
    "form"
  ]
}