Tag input
Free-text-first sibling of the multi Combobox. The user types arbitrary strings (IPs, webhook URLs, allowed origins, custom tags) and each becomes a removable chip when they hit a delimiter.
Anatomy
The container matches the Input field visual — same border, same focus ring. Inside, three optional chrome pieces surround the inline text input.
- 1Container
h-9 min, border-input, rounded-md, no shadow, focus ring-[3px] ring-ring/50 — matches Input.
- 2Delete all pill
Filled muted with font-semibold. Visible only when there is at least one tag.
- 3Tag chip
Outline + bg-background + text-foreground, matching <Combobox multiple>. Bold X on the right removes the chip.
- 4+N overflow
Same outline as a chip. Shown when the number of tags exceeds maxChips.
- 5Inline input
Free-text field. Delimiter keys (Enter / , / ; / space) commit the pending value as a chip. Backspace on an empty input removes the last chip.
Recipes
Canonical compositions. Copy the snippet, adjust props, ship it.
Free-text IP list using the default delimiters (Enter / , / ; / space). Try typing 10.0.0.1 then Enter.
<TagInput defaultValue={["192.158.1.38"]} placeholder="Enter IP address" />A candidate is silently rejected when validate returns false. Try typing not-an-ip and pressing Enter — nothing happens.
<TagInput
placeholder="Enter IP address"
validate={(v) => /^\d{1,3}(\.\d{1,3}){3}$/.test(v)}
/>Collapse tags past maxChips into a +N pill. Useful in tight rows (e.g. inside a table cell).
<TagInput
defaultValue={["a", "b", "c", "d", "e"]}
maxChips={3}
/>For emails or paths where commas / spaces are meaningful. Only Enter commits a tag.
<TagInput placeholder="Enter email" delimiters={["Enter"]} />Import
import { TagInput } from "@/components/ui/tag-input";Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string[] | — | Controlled tags list. |
| defaultValue | string[] | — | Uncontrolled initial tags. |
| onValueChange | (tags: string[]) => void | — | Fires on any change (add, remove, clear). |
| placeholder | string | "Enter a value" | Placeholder shown in the inline input when the field is empty. |
| delimiters | string[] | ["Enter", ",", ";", " "] | Keys that commit the pending input as a tag. |
| clearable | boolean | true | Show the 'Delete all' pill when there are tags. |
| validate | (candidate: string) => boolean | — | Return false to silently reject a candidate. |
| format | (candidate: string) => string | — | Formatter applied before validation (lowercase, strip, etc.). Trim is always applied first. |
| deduplicate | boolean | true | Reject duplicates. |
| maxChips | number | — | Max chips visible before collapsing extras into +N. |
| maxTags | number | — | Hard cap on total number of tags. |
| disabled | boolean | — | Disable the whole field. |
When to use
- User needs to enter multiple free-text values into a single field.
- Values are validated per-item (IP, URL, email, hostname, custom string).
- You want the chip affordance + Delete all shortcut but the list is open-ended.
When not to use
- User picks from a fixed list of options — use
<Combobox multiple>. - Single free-text value — use
<Input>. - Long paragraphs — use
<Textarea>.