Back to catalog
Component detail entry
Forms
@vean/ui
STagsInput

TagsInput

TagsInput belongs to the Forms category. Start with demos and usage, then use the API tables to finish integration and styling decisions.

Use the quick jump panel to reach overview, usage, demos, and API first, then continue through the full page.

Quick jump

Related components

If you are composing interactions in the same family, these components usually pair well together.

Tags Input

Overview

A composable multi-value input for adding, displaying, and removing tags. Supports controlled/uncontrolled string[] values, tag creation via Enter/Tab/blur/delimiter typing/paste, full keyboard selection and removal (with RTL reversal), max/duplicate constraints, and hidden form-input serialization. Use it for keyword lists, email recipients, or any small-token collection that needs fast keyboard workflows instead of a plain text input.

Usage

Features

  • 🏷 Tag model — controlled/uncontrolled string[] via useControllableState; add/remove/clear events plus per-action addTag/removeTag emits
  • ⌨️ Full keyboard support — ArrowLeft/ArrowRight select the adjacent tag (logical direction, reversed in RTL), Backspace/Delete remove the selected tag, Home/End jump to the first/last tag, typing deselects
  • ✂️ Delimiter & paste — commit the typed value when a delimiter (string or RegExp) is entered, or split pasted text (addOnPaste)
  • ⏎ Commit triggers — Enter always commits; Tab and blur commit only with addOnTab/addOnBlur
  • 🚦 Constraints — max caps the total count, duplicate allows repeated values, displayValue rewrites the label; rejected values emit invalid
  • 🧩 Composable parts — Root/Control/Item/ItemText/ItemDelete/Clear with *Props passthrough on the compact and a fully replaceable item slot
  • 📋 Form integration — name/required/disabled serialize through a visually hidden input; aria-controls links the input to the tag list container
  • 🔤 Localized UI — default aria-labels come from locale messages and can be overridden per part
  • 🚫 Disabled / readonly — disabled blocks all interaction; readonly blocks typing but keeps tag removal and clearing available

Component family

  • STagsInput — the styled wrapper that forwards props to the Aria compact and injects tagsInputVariants classes (6 slots: root/item/itemText/itemDelete/control/clear)
  • TagsInputCompact (Aria) — data-driven composition of TagsInputRoot + one TagsInputItem per value (default item text/delete) + Control + Clear (gated by clearable); import from @vean/aria/tags-input for unstyled usage
  • TagsInputRoot (Aria) — state owner: useControllableState for the value array, collection registration, keydown/removal/clear logic, and the visually hidden form input
  • TagsInputControl (Aria) — the committed input: delimiter typing, paste splitting, Enter/Tab/blur commit, composition guard
  • TagsInputItem / TagsInputItemText / TagsInputItemDelete (Aria) — a registered collection item with its label span and keyboard-reachable delete button
  • TagsInputClear (Aria) — the clear-all button, hidden until the root has a value (styling) and gated by clearable (rendering)

Demos

Basic

Vue
React
Angular

API

Aria Layer

8

TagsInput

31 Props4 Emits1 Slots
Props
PropTypeDefaultDescription
class
ClassValue
—
Additional class names applied to the root element.
size
ThemeSize
—
Visual size of the component.
ui
Partial<TagsInputUi>
—
Per-slot class overrides for the component.
clearable
boolean
—
Whether to render the clear trigger.
controlProps
TagsInputControlProps
—
Props forwarded to the input element.
clearProps
TagsInputClearProps
—
Props forwarded to the clear element.
itemProps
TagsInputItemProps
—
Props forwarded to the item element.
itemTextProps
TagsInputItemTextProps
—
Props forwarded to the item text element.
itemDeleteProps
TagsInputItemDeleteProps
—
Props forwarded to the item delete element.
dir
Direction
—
The reading direction of the tags input.
modelValue
string[]
—
The controlled value of the tags input.
defaultValue
string[]
—
The uncontrolled default value of the tags input.
addOnPaste
boolean
—
Whether to add tags on paste.
addOnTab
boolean
—
Whether to add tags on tab.
addOnBlur
boolean
—
Whether to add tags on blur.
duplicate
boolean
—
Whether to allow duplicated tags.
delimiter
string | RegExp
—
The delimiter used to add tags.
max
number
—
Maximum number of tags. Set to 0 for unlimited.
displayValue
((value: string) => string)
"(value: string) => value"
Display the value of the tag. Useful when you want to apply modifications to the value like adding a suffix
id
string
—
Id of the input element
autofocus
boolean
—
When `true`, the input is auto-focused.
autocomplete
'search' | 'name' | 'email' | 'tel' | 'url' | 'on' | 'off' | 'username' | 'current-password' | 'new-password' | 'one-time-code' | 'given-name' | 'family-name' | 'organization' | 'street-address' | 'address-level2' | 'postal-code' | 'country' | 'cc-name' | 'cc-number' | 'cc-exp' | 'cc-csc' | 'transaction-amount'
—
The autocomplete attribute of the input. Supports the HTML autofill tokens (including password-manager values such as `current-password` / `new-password`), in addition to the `on` / `off` switches.
disabled
boolean
—
When `true`, prevents the user from interacting with the input.
maxlength
number
—
The maximum number of characters allowed in the input
minlength
number
—
The minimum number of characters allowed in the input
pattern
string
—
The pattern attribute of the input
placeholder
string
—
The placeholder of the input
readonly
boolean
—
When `true`, the input is read-only.
type
InputTypeHTMLAttribute
—
The type of the input element.
name
string
—
The name of the field. Submitted with its owning form as part of a name/value pair.
required
boolean
—
When `true`, indicates that the user must set the value before the owning form can be submitted.
Emits
Emit NameParametersDescription
update:modelValue
string[]
Emitted when the model value changes.
invalid
string
Emitted when invalid occurs.
addTag
string
Emitted when add tag occurs.
removeTag
string
Emitted when remove tag occurs.
Slots
Slot NameParametersDescription
item
TagsInputCompactSlotProps
Custom item content or full item replacement.

TagsInputClear

4 Props
Props
PropTypeDefaultDescription
type
ButtonType
'button'
The type of the button element. Can be one of 'button', 'submit', or 'reset'.
disabled
boolean
—
Whether the component is disabled.
asChild
boolean
—
Change the default rendered element for the one passed as a child, merging their props and behavior.
as
AsTag | Component
'div'
The element or component this component should render as. Can be overwrite by `asChild`

TagsInputCompact

28 Props4 Emits1 Slots
Props
PropTypeDefaultDescription
clearable
boolean
—
Whether to render the clear trigger.
controlProps
TagsInputControlProps
—
Props forwarded to the input element.
clearProps
TagsInputClearProps
—
Props forwarded to the clear element.
itemProps
TagsInputItemProps
—
Props forwarded to the item element.
itemTextProps
TagsInputItemTextProps
—
Props forwarded to the item text element.
itemDeleteProps
TagsInputItemDeleteProps
—
Props forwarded to the item delete element.
dir
Direction
—
The reading direction of the tags input.
modelValue
string[]
—
The controlled value of the tags input.
defaultValue
string[]
—
The uncontrolled default value of the tags input.
addOnPaste
boolean
—
Whether to add tags on paste.
addOnTab
boolean
—
Whether to add tags on tab.
addOnBlur
boolean
—
Whether to add tags on blur.
duplicate
boolean
—
Whether to allow duplicated tags.
delimiter
string | RegExp
—
The delimiter used to add tags.
max
number
—
Maximum number of tags. Set to 0 for unlimited.
displayValue
((value: string) => string)
"(value: string) => value"
Display the value of the tag. Useful when you want to apply modifications to the value like adding a suffix
id
string
—
Id of the input element
autofocus
boolean
—
When `true`, the input is auto-focused.
autocomplete
'search' | 'name' | 'email' | 'tel' | 'url' | 'on' | 'off' | 'username' | 'current-password' | 'new-password' | 'one-time-code' | 'given-name' | 'family-name' | 'organization' | 'street-address' | 'address-level2' | 'postal-code' | 'country' | 'cc-name' | 'cc-number' | 'cc-exp' | 'cc-csc' | 'transaction-amount'
—
The autocomplete attribute of the input. Supports the HTML autofill tokens (including password-manager values such as `current-password` / `new-password`), in addition to the `on` / `off` switches.
disabled
boolean
—
When `true`, prevents the user from interacting with the input.
maxlength
number
—
The maximum number of characters allowed in the input
minlength
number
—
The minimum number of characters allowed in the input
pattern
string
—
The pattern attribute of the input
placeholder
string
—
The placeholder of the input
readonly
boolean
—
When `true`, the input is read-only.
type
InputTypeHTMLAttribute
—
The type of the input element.
name
string
—
The name of the field. Submitted with its owning form as part of a name/value pair.
required
boolean
—
When `true`, indicates that the user must set the value before the owning form can be submitted.
Emits
Emit NameParametersDescription
update:modelValue
string[]
Emitted when the model value changes.
invalid
string
Emitted when invalid occurs.
addTag
string
Emitted when add tag occurs.
removeTag
string
Emitted when remove tag occurs.
Slots
Slot NameParametersDescription
item
TagsInputCompactSlotProps
Custom item content or full item replacement.

TagsInputControl

TagsInputItem

4 Props
Props
PropTypeDefaultDescription
value
string
—
Value associated with the tag.
disabled
boolean
—
Whether to disable the tag item.
asChild
boolean
—
Change the default rendered element for the one passed as a child, merging their props and behavior.
as
AsTag | Component
'div'
The element or component this component should render as. Can be overwrite by `asChild`

TagsInputItemDelete

4 Props
Props
PropTypeDefaultDescription
type
ButtonType
'button'
The type of the button element. Can be one of 'button', 'submit', or 'reset'.
disabled
boolean
—
Whether the component is disabled.
asChild
boolean
—
Change the default rendered element for the one passed as a child, merging their props and behavior.
as
AsTag | Component
'div'
The element or component this component should render as. Can be overwrite by `asChild`

TagsInputItemText

2 Props
Props
PropTypeDefaultDescription
asChild
boolean
—
Change the default rendered element for the one passed as a child, merging their props and behavior.
as
AsTag | Component
'div'
The element or component this component should render as. Can be overwrite by `asChild`

TagsInputRoot

22 Props4 Emits
Props
PropTypeDefaultDescription
dir
Direction
—
The reading direction of the tags input.
modelValue
string[]
—
The controlled value of the tags input.
defaultValue
string[]
—
The uncontrolled default value of the tags input.
addOnPaste
boolean
—
Whether to add tags on paste.
addOnTab
boolean
—
Whether to add tags on tab.
addOnBlur
boolean
—
Whether to add tags on blur.
duplicate
boolean
—
Whether to allow duplicated tags.
delimiter
string | RegExp
—
The delimiter used to add tags.
max
number
—
Maximum number of tags. Set to 0 for unlimited.
displayValue
((value: string) => string)
"(value: string) => value"
Display the value of the tag. Useful when you want to apply modifications to the value like adding a suffix
id
string
—
Id of the input element
autofocus
boolean
—
When `true`, the input is auto-focused.
autocomplete
'search' | 'name' | 'email' | 'tel' | 'url' | 'on' | 'off' | 'username' | 'current-password' | 'new-password' | 'one-time-code' | 'given-name' | 'family-name' | 'organization' | 'street-address' | 'address-level2' | 'postal-code' | 'country' | 'cc-name' | 'cc-number' | 'cc-exp' | 'cc-csc' | 'transaction-amount'
—
The autocomplete attribute of the input. Supports the HTML autofill tokens (including password-manager values such as `current-password` / `new-password`), in addition to the `on` / `off` switches.
disabled
boolean
—
When `true`, prevents the user from interacting with the input.
maxlength
number
—
The maximum number of characters allowed in the input
minlength
number
—
The minimum number of characters allowed in the input
pattern
string
—
The pattern attribute of the input
placeholder
string
—
The placeholder of the input
readonly
boolean
—
When `true`, the input is read-only.
type
InputTypeHTMLAttribute
—
The type of the input element.
name
string
—
The name of the field. Submitted with its owning form as part of a name/value pair.
required
boolean
—
When `true`, indicates that the user must set the value before the owning form can be submitted.
Emits
Emit NameParametersDescription
update:modelValue
string[]
Emitted when the model value changes.
invalid
string
Emitted when invalid occurs.
addTag
string
Emitted when add tag occurs.
removeTag
string
Emitted when remove tag occurs.

Notes

Architecture and benchmark differences

TagsInputRoot owns the value via useControllableState and the collection registry (each Item registers through useCollectionItem). TagsInputControl only commits values: it watches delimiter/paste/Enter/Tab/blur, trims and validates through the root’s onAddValue, and reports rejections via the invalid event. Keyboard selection state (selectedElement) lives on the root so the control’s keydown handler can move selection with arrow keys (logical direction, RTL-aware) and remove tags with Backspace/Delete/Home/End. The compact iterates the value array with an index-value key so duplicated values stay stable without in-place value patching. Most benchmark libraries expose tags as a mode of a single select; the Aria/styled split, per-part *Props passthrough, slot-driven item rendering, and the invalid/addTag/removeTag event surface are the differentiators.

CapabilityVeanUIAnt DesignElement PlusMantineNaive UIshadcn
Aria/styled split✅—————
Controlled / uncontrolled✅✅✅✅✅✅
Enter / delimiter / paste add✅✅✅✅✅✅
Keyboard tag selection + removal✅——✅——
RTL direction reversal✅—————
max / duplicate constraints✅✅✅✅✅—
RegExp delimiter✅—————
Per-part *Props passthrough✅—————
Custom item slot✅✅✅✅—✅
Form hidden-input serialization✅—————
Localized aria-labels✅✅✅✅✅—

Cautions

  • The value is always a string[]; duplicates are rejected unless duplicate is set. displayValue changes the label only — the stored value stays the raw string.
  • max={0} means unlimited; any positive number caps the count and rejected additions emit invalid with the attempted value.
  • addOnBlur commits pending text on blur, but a blur that lands on a tag’s delete/clear button inside the tag list is skipped (guarded via aria-controls).
  • addOnTab prevents the default tab-out and commits instead; if you need to leave the field, disable it or commit on blur.
  • The compact renders the clear trigger whenever clearable is not false; the default styles hide it until the root has a value.
  • In controlled mode (modelValue bound), the parent must update the bound array — the component only emits update:modelValue.
  • readonly keeps the input read-only but still allows tag removal and clearing; use disabled to block all interaction.
  • The input’s aria-controls points at the derived tag-list container id ({id}-tags-list); pass an id to enable the blur guard and form-label association.

FAQ

How do I add tags on Enter only?

That’s the default — type a value and press Enter. Tab/blur commit only when addOnTab/addOnBlur are enabled.

How do I let users paste multiple tags at once?

Set addOnPaste and keep the default delimiter="," — pasting "a,b,c" creates three tags. Use a RegExp delimiter for flexible splits, e.g. :delimiter="/[,,]/".

How do I prevent duplicate tags?

Duplicates are rejected by default and emit invalid with the rejected value. Pass duplicate to allow repeats (the compact keys each item by its position so repeats stay stable).

How does the value reach a form submission?

With name set, the values serialize into a visually hidden input (one per tag). Combined with required, an empty tag list marks the control invalid.

Why did my controlled value not update?

In controlled mode the component only emits update:modelValue; you must rebind it (e.g. v-model) so the prop changes, otherwise the UI stays on the previous array.

Changelog

Version history is grouped by release so you can quickly see what changed for this component across published versions.

v0.19.0

2026-05-11

Compare release

Feature

Implement TagsInputCompact

Scope: tags-inputAuthors: soybeanjs, soybeanfeView commitec340