Free-form multi-value entry: post tags, contact segments, skills, invoice recipients, keywords. Chips sit inside the box with a trailing text input; Enter, Tab or comma add, pasted lists split, duplicates are caught with locale folding, and every add or removal is announced.
- Product design
- Case study
Press Enter or comma to add. Backspace on the empty box edits the last tag.
Installation
The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";
Usage
import { Field, TagInput } from "@vamos-ui/react";
<Field label="Tags">
<TagInput name="tags" suggestions={existingTags} max={10} />
</Field>
<TagInput validate={(v) => /^\S+@\S+\.\S+$/.test(v) || "Not an email address"} />Keyboard: Backspace on the empty input moves focus to the last chip; a second Backspace (or Delete) removes it. ←/→ move between chips and back to the input; typing on a chip returns to the input.
Chips are a list (role=list) with one remove button each; the input is a combobox only when suggestions are given.
Typing a duplicate pulses the existing chip (no animation under reduced motion) and keeps the text for editing.
Hierarchical categories belong in Tree select, not Tag input.
Examples
Suggestions (CRM segments)
suggestions turns the input into a combobox: ↑/↓ move, Enter picks. Already-added tags are hidden. tagTone colours chips per tag. Pass a function (query, signal) for server suggestions.
- Enterprise
Pick from existing segments or create a new one.
Recipients with validation
validate keeps invalid entries as danger chips (with the message for screen readers) and marks the field invalid. Space and semicolon are delimiters here; addOnBlur commits what's left.
- accounts@northwind.com
- maria.santos@northwind, Not a valid email address
- finance@brightlabs.de
Invalid addresses are marked red and block sending.
Maximum (SEO keywords)
max shows a counter and makes the input read-only when full; the limit is part of the description. maxLength rejects long tags.
- booking software
- online appointments
- salon scheduling
- spa
Search engines read at most five; extra ones are ignored.
Paste a list (HR)
Pasting splits on commas, semicolons, tabs and newlines — straight from a spreadsheet column.
- Figma
Paste a comma-, semicolon- or line-separated list. Duplicates are skipped.
Figma, React, TypeScript SQL; Data visualisation User research
Sizes & states
sm and md, empty, error from Field, read-only (no remove buttons), disabled and ghost.
- draft
- q4
Add at least one tag.
- Enterprise
- EMEA
Synced from the CRM.
- Archived
- inline
- toolbar
API
TagInput
| Prop | Type | Default |
|---|---|---|
value / defaultValueTags (controlled / uncontrolled). | string[] | [] |
onValueChangeCalled with the next array. | (tags: string[]) => void | — |
suggestionsStatic list (locale-folded filter) or a debounced, abortable loader. | string[] | (query, signal) => Promise<string[]> | — |
allowCustomfalse: only suggestions can be added. | boolean | true |
delimitersKeys that commit. Paste always splits on , ; tab and newline. | string[] | ["Enter", "Tab", ","] |
maxMaximum number of tags; shows a counter. | number | — |
maxLengthMaximum characters per tag. | number | — |
validateError message for an invalid tag; kept as a danger chip. | (tag) => string | true | — |
transformNormalise before adding (e.g. lowercase emails). | (tag) => string | trim + collapse spaces |
dedupefold treats “İstanbul” and “istanbul” as the same tag (current locale). | "fold" | "exact" | "fold" |
tagToneChip tone per tag. | (tag) => BadgeProps["tone"] | — |
addOnBlurCommit the typed text when focus leaves. | boolean | false |
nameOne hidden input per tag (formData.getAll(name)). | string | — |
placeholderShown while there are no tags. | string | "Add a tag…" |
size / variantBox look from controlVariants. | "sm" | "md" · "default" | "ghost" | "md" · "default" |
disabled / readOnly / invalid / requiredAlso inherited from Field. | boolean | — |
labelsplaceholder, list, suggestions, added, removed, duplicate, max, notAllowed, tooLong, invalid, remove, loading. | Partial<TagInputLabels> | — |