Tags Input
A free-form chip input with paste splitting, validation, and duplicate handling.
Labels
- design
- urgent
["design","urgent"]
Recipients
Try pasting a@b.com, c@d.com.
Installation
Usage
For values the user types rather than picks: labels, keywords, recipients, allowed domains. Where
multi-select chooses from a known list, this one accepts anything that
passes your validate.
import { TagsInput } from "@/components/ui/tags-input";
export function LabelField() {
const [labels, setLabels] = React.useState<string[]>(["bug"]);
return <TagsInput value={labels} onValueChange={setLabels} placeholder="Add a label…" />;
}The value is a string[], so it goes straight into a form body or a database column.
Committing a tag
Enter commits. So does any character in delimiters, which defaults to comma and space — set it to
[","] alone when tags may contain spaces:
<TagsInput delimiters={[","]} placeholder="Add a phrase…" />Backspace on an empty input removes the last tag. Blur commits the draft by default; pass
blurBehavior="clear" to discard it, or "keep" to leave it in place.
Pasting
Pasting text that contains delimiters, newlines, or tabs adds every part at once, which is what makes this usable for a column copied out of a spreadsheet:
alice@example.com, bob@example.com, carol@example.comA paste with no delimiter is left alone as ordinary text, so a single value can still be edited before committing.
Validation
validate returns true to accept, or a message to reject. The message renders under the field and
is wired up with aria-describedby.
<TagsInput
validate={(tag) => (tag.includes("@") ? true : `${tag} is not an email address.`)}
onValueChange={setRecipients}
/>Duplicates are rejected silently and the existing chip flashes instead — repeating "already added" as an error is noise for something the user can see.
Props
| Prop | Description |
|---|---|
value / defaultValue / onValueChange | Controlled or uncontrolled string[]. |
delimiters | Characters that commit the draft. Defaults to [",", " "]. |
max | Maximum number of tags. The input disables at the limit. |
allowDuplicates | Permit repeated values. Off by default. |
validate | (tag) => true | string. A string is shown as the error message. |
blurBehavior | "add" (default), "clear", or "keep". |
name | Emits one hidden input per tag, so the field posts in a plain HTML form. |
aria-label | Labels the chip list. Defaults to "Tags". |
Accessibility
Tags are a list of li elements, each with its own labelled remove button, so a screen reader
announces the count and reads every tag. Rejections use role="alert". Clicking anywhere in the
field forwards focus to the input.