ComponentsForms & inputs

Tag input

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.

Badge chips + Radix Popover (suggestions) components/tag-input.tsx, lib/use-async-list.ts, lib/text.ts, components/badge.tsx, styles/forms.csstheme · material · accent · density · font
  • Product design
  • Case study

Press Enter or comma to add. Backspace on the empty box edits the last tag.

Installation

$ pnpm add @vamos-ui/react

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
Up to 5 tags

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

PropTypeDefault
value / defaultValue
Tags (controlled / uncontrolled).
string[][]
onValueChange
Called with the next array.
(tags: string[]) => void—
suggestions
Static list (locale-folded filter) or a debounced, abortable loader.
string[] | (query, signal) => Promise<string[]>—
allowCustom
false: only suggestions can be added.
booleantrue
delimiters
Keys that commit. Paste always splits on , ; tab and newline.
string[]["Enter", "Tab", ","]
max
Maximum number of tags; shows a counter.
number—
maxLength
Maximum characters per tag.
number—
validate
Error message for an invalid tag; kept as a danger chip.
(tag) => string | true—
transform
Normalise before adding (e.g. lowercase emails).
(tag) => stringtrim + collapse spaces
dedupe
fold treats “İstanbul” and “istanbul” as the same tag (current locale).
"fold" | "exact""fold"
tagTone
Chip tone per tag.
(tag) => BadgeProps["tone"]—
addOnBlur
Commit the typed text when focus leaves.
booleanfalse
name
One hidden input per tag (formData.getAll(name)).
string—
placeholder
Shown while there are no tags.
string"Add a tag…"
size / variant
Box look from controlVariants.
"sm" | "md" · "default" | "ghost""md" · "default"
disabled / readOnly / invalid / required
Also inherited from Field.
boolean—
labels
placeholder, list, suggestions, added, removed, duplicate, max, notAllowed, tooLong, invalid, remove, loading.
Partial<TagInputLabels>—