

<Preview name="DefaultTagInputDemo" />

## Overview [#overview]

`TagInput` lets users add, remove, and select multiple tags. It supports predefined options, free-typed tags via Enter/comma, CSV/TSV/newline pasting, and backspace-to-remove on an empty input.

## Usage [#usage]

```tsx
import { TagInput } from "@tilt-legal/cubitt-components/tag-input";
```

```tsx
const options = [
  { id: "react", label: "React" },
  { id: "ts", label: "TypeScript" },
  { id: "ui", label: "UI/UX" },
];

export default function Component() {
  return <TagInput options={options} placeholder="e.g. TypeScript" />;
}
```

<Accordions type="single">
  <Accordion title="URL State Management">
    Sync tags with the URL by providing `paramName`. The component serializes to a string array automatically.

    ```tsx
    // Synced with ?demo-tags=foo,bar (when tags are added)
    <TagInput paramName="demo-tags" placeholder="Add tags..." />

    // Advanced options
    <TagInput
      paramName="labels"
      paramClearOnDefault
      paramDebounce={300}
      onUrlValueChange={(value) => console.log("tags:", value)}
    />
    ```
  </Accordion>
</Accordions>

## Examples [#examples]

### Default (predefined options) [#default-predefined-options]

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="DefaultTagInputDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { TagInput } from "@tilt-legal/cubitt-components/tag-input";

    const options = [
      { id: "react", label: "React" },
      { id: "ts", label: "TypeScript" },
      { id: "ui", label: "UI/UX" },
    ];

    export default function Component() {
      return <TagInput options={options} placeholder="Add tags" />;
    }
    ```
  </Tab>
</Tabs>

### Free entry + remove last on backspace [#free-entry--remove-last-on-backspace]

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="FreeEntryTagInputDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { useState } from "react";
    import { TagInput } from "@tilt-legal/cubitt-components/tag-input";

    export default function Component() {
      const [tags, setTags] = useState([]);
      return (
        <TagInput
          tags={tags}
          onTagsChange={setTags}
          placeholder="Type and press Enter or ,"
        />
      );
    }
    ```
  </Tab>
</Tabs>

### Paste CSV/TSV/newlines [#paste-csvtsvnewlines]

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="PasteTagInputDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { TagInput } from "@tilt-legal/cubitt-components/tag-input";

    export default function Component() {
      return (
        <TagInput
          enableExcelPaste
          pasteDelimiters={[",", "\n", "\t"]}
          placeholder="Paste a CSV/TSV list"
        />
      );
    }
    ```
  </Tab>
</Tabs>

### Expanded field [#expanded-field]

Increase the field height when tags are likely to wrap across several rows or when you want a larger area to click and start typing.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="ExpandedTagInputDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { useState } from "react";
    import { TagInput } from "@tilt-legal/cubitt-components/tag-input";

    const initialTags = [
      { id: "contract-review", label: "Contract Review" },
      { id: "employment", label: "Employment" },
      { id: "urgent", label: "Urgent" },
    ];

    export default function Component() {
      const [tags, setTags] = useState(initialTags);

      return (
        <TagInput
          className="w-full max-w-xs content-start items-start min-h-28"
          onTagsChange={setTags}
          placeholder="Add tags"
          tags={tags}
        />
      );
    }
    ```
  </Tab>
</Tabs>

### URL State [#url-state]

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="URLStateTagInputDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { TagInput } from "@tilt-legal/cubitt-components/tag-input";

    // Synced with ?demo-tags=foo,bar
    export default function Component() {
      return <TagInput paramName="demo-tags" placeholder="Synced with ?demo-tags=" />;
    }
    ```
  </Tab>
</Tabs>

## API Reference [#api-reference]

### TagInput [#taginput]

| Prop                   | Type                                                                                             | Default                     | Description                                   |
| ---------------------- | ------------------------------------------------------------------------------------------------ | --------------------------- | --------------------------------------------- |
| `tags`                 | `{ id: string; label: string }[]`                                                                | —                           | Controlled tags.                              |
| `defaultTags`          | `{ id: string; label: string }[]`                                                                | —                           | Uncontrolled initial tags.                    |
| `onTagsChange`         | `(next: Tag[]) => void`                                                                          | —                           | Change handler for controlled usage.          |
| `options`              | `{ id: string; label: string }[]`                                                                | —                           | Predefined selectable options.                |
| `size`                 | `"sm" \| "md" \| "lg"`                                                                           | `"md"`                      | Input size.                                   |
| `placeholder`          | `string`                                                                                         | —                           | Placeholder for the input.                    |
| `className`            | `string`                                                                                         | —                           | Additional className for the visible control. |
| `inputProps`           | `Omit<InputHTMLAttributes<HTMLInputElement>, "defaultValue" \| "onChange" \| "size" \| "value">` | —                           | Props forwarded to the inner input.           |
| `allowDuplicates`      | `boolean`                                                                                        | `false`                     | Allow duplicate tag labels.                   |
| `enableExcelPaste`     | `boolean`                                                                                        | `true`                      | Split pasted CSV/TSV/newlines into tags.      |
| `enableCommaDelimiter` | `boolean`                                                                                        | `true`                      | Comma key creates a tag.                      |
| `pasteDelimiters`      | `string[]`                                                                                       | `[",", "\n", "\r\n", "\t"]` | Delimiters used when splitting pasted text.   |

#### URL State Props [#url-state-props]

When `paramName` is provided, `TagInput` syncs its tags to a string\[] URL parameter via TanStack Router search params.

| Prop                  | Type                                | Default | Description                                               |
| --------------------- | ----------------------------------- | ------- | --------------------------------------------------------- |
| `paramName`           | `string`                            | —       | URL parameter name to sync. Enables URL state management. |
| `onUrlValueChange`    | `(value: string[] \| null) => void` | —       | Callback when URL parameter value changes.                |
| `paramClearOnDefault` | `boolean`                           | `true`  | Remove URL param when value equals default.               |
| `paramThrottle`       | `number`                            | —       | Throttle URL updates in milliseconds.                     |
| `paramDebounce`       | `number`                            | —       | Debounce URL updates in milliseconds.                     |
