Accessible, unstyled TagsInput with typed tags, delimiters, paste splitting, and form association.
|
|
| Package |
@vizejs/ui/tags-input |
| Maturity |
stable |
| Own the source |
vize lib pull tags-input |
| Requires |
context, controllable-state, id |
| Aliases |
tags input, chips input, token field, tag editor |
| Covers |
Reka UI TagsInput, Ark UI Tags Input, Mantine TagsInput |
Usage
import { TagsInput, TagsInputRoot, TagsInputInput, TagsInputItem, TagsInputItemDelete, TagsInputItemText } from "@vizejs/ui/tags-input";
Or copy the source into your project with vize lib pull tags-input (see Source Distribution).
API
Source: src/families/form/tags-input/tags-input-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Id of the native text input, so a <label for> can name the field. null and undefined select a deterministic fallback. |
modelValue |
readonly T[] |
undefined |
Controlled tags. undefined selects uncontrolled behavior. |
defaultValue |
readonly T[] |
[] |
Initial uncontrolled tags and the value restored by reset and native form reset. |
parseTag |
(text: string) => T | null |
undefined |
Convert trimmed input text into a tag, or return null to reject it with reason "parse". Omitting it keeps the text as-is, which is only sound for string tags; non-string tag types must provide a parser. |
tagText |
(tag: T) => string |
String(tag) |
Display and form-submission text for one tag. |
by |
TagsInputBy<T> |
Object.is |
Duplicate-detection equality: an object property name or a comparator. |
validate |
TagsInputValidator<T> |
undefined |
Custom validation. Return false or a message string to reject a candidate. |
delimiters |
readonly string[] |
[","] |
Strings that commit the current text. Enter always commits. |
addOnPaste |
boolean |
true |
Split pasted text on delimiters, line breaks, and tabs into several tags. |
addOnBlur |
boolean |
false |
Commit pending text when the input loses focus. |
allowDuplicates |
boolean |
false |
Accept tags equal to an existing tag under by. |
max |
number |
undefined |
Maximum number of tags. undefined means unlimited. |
editable |
boolean |
false |
Allow Enter, F2, or double-click on a focused tag to edit it inline. |
disabled |
boolean |
false |
Disable input, tag focus, removal, and form submission. |
readonly |
boolean |
false |
Keep tags focusable and submitted while preventing every change. |
required |
boolean |
false |
Require at least one tag for native constraint validation. |
name |
string |
undefined |
Native form field name. Each tag submits one entry under this name. |
form |
string |
undefined |
Id of the form owning the hidden submission inputs. |
dir |
TagsInputDirection |
"ltr" |
Reading direction used by ArrowLeft and ArrowRight between tags. |
ariaLabel |
string |
undefined |
Accessible name for the text input when no label supplies one. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the text input. |
ariaDescribedby |
string |
undefined |
Space-separated ids that describe the text input. |
ariaErrormessage |
string |
undefined |
Id of the validation error message used while invalid. |
ariaInvalid |
TagsInputAriaInvalid |
false |
Invalid state announced on the text input. |
Events
| Event |
Payload |
Description |
update:modelValue |
[value: readonly T[]] |
Fired when the tag list requests a new controlled value. |
add |
[tag: T, index: number, source: TagsInputAddSource] |
Fired after a tag is accepted, with its index and input source. |
remove |
[tag: T, index: number, source: TagsInputRemoveSource] |
Fired after a tag is removed, with its former index and input source. |
edit |
[tag: T, previous: T, index: number] |
Fired after an inline edit replaces a tag. |
invalid |
[event: TagsInputInvalidEvent<T>] |
Fired when a candidate tag is rejected by parsing, duplicates, max, or validation. |
Slots
| Slot |
Slot props |
Description |
default |
TagsInputSlotState<T> |
TagsInputItem children and the TagsInputInput. Receives the current tags and state. |
Exposed
| Member |
Type |
Description |
add |
— |
|
addTag |
— |
|
clear |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
focus |
— |
|
id |
— |
|
inputValue |
— |
|
remove |
— |
|
reset |
— |
|
setInputValue |
— |
|
state |
— |
|
tags |
— |
|
Source: src/families/form/tags-input/tags-input-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Id of the native text input, so a <label for> can name the field. null and undefined select a deterministic fallback. |
modelValue |
readonly T[] |
undefined |
Controlled tags. undefined selects uncontrolled behavior. |
defaultValue |
readonly T[] |
[] |
Initial uncontrolled tags and the value restored by reset and native form reset. |
parseTag |
(text: string) => T | null |
undefined |
Convert trimmed input text into a tag, or return null to reject it with reason "parse". Omitting it keeps the text as-is, which is only sound for string tags; non-string tag types must provide a parser. |
tagText |
(tag: T) => string |
String(tag) |
Display and form-submission text for one tag. |
by |
TagsInputBy<T> |
Object.is |
Duplicate-detection equality: an object property name or a comparator. |
validate |
TagsInputValidator<T> |
undefined |
Custom validation. Return false or a message string to reject a candidate. |
delimiters |
readonly string[] |
[","] |
Strings that commit the current text. Enter always commits. |
addOnPaste |
boolean |
true |
Split pasted text on delimiters, line breaks, and tabs into several tags. |
addOnBlur |
boolean |
false |
Commit pending text when the input loses focus. |
allowDuplicates |
boolean |
false |
Accept tags equal to an existing tag under by. |
max |
number |
undefined |
Maximum number of tags. undefined means unlimited. |
editable |
boolean |
false |
Allow Enter, F2, or double-click on a focused tag to edit it inline. |
disabled |
boolean |
false |
Disable input, tag focus, removal, and form submission. |
readonly |
boolean |
false |
Keep tags focusable and submitted while preventing every change. |
required |
boolean |
false |
Require at least one tag for native constraint validation. |
name |
string |
undefined |
Native form field name. Each tag submits one entry under this name. |
form |
string |
undefined |
Id of the form owning the hidden submission inputs. |
dir |
TagsInputDirection |
"ltr" |
Reading direction used by ArrowLeft and ArrowRight between tags. |
ariaLabel |
string |
undefined |
Accessible name for the text input when no label supplies one. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the text input. |
ariaDescribedby |
string |
undefined |
Space-separated ids that describe the text input. |
ariaErrormessage |
string |
undefined |
Id of the validation error message used while invalid. |
ariaInvalid |
TagsInputAriaInvalid |
false |
Invalid state announced on the text input. |
Events
| Event |
Payload |
Description |
update:modelValue |
[value: readonly T[]] |
Fired when the tag list requests a new controlled value. |
add |
[tag: T, index: number, source: TagsInputAddSource] |
Fired after a tag is accepted, with its index and input source. |
remove |
[tag: T, index: number, source: TagsInputRemoveSource] |
Fired after a tag is removed, with its former index and input source. |
edit |
[tag: T, previous: T, index: number] |
Fired after an inline edit replaces a tag. |
invalid |
[event: TagsInputInvalidEvent<T>] |
Fired when a candidate tag is rejected by parsing, duplicates, max, or validation. |
Slots
| Slot |
Slot props |
Description |
default |
TagsInputSlotState<T> |
TagsInputItem children and the TagsInputInput. Receives the current tags and state. |
Exposed
| Member |
Type |
Description |
element |
HTMLDivElement | null |
Rendered root element. |
id |
string |
Id used by the text input. |
tags |
readonly T[] |
Current tags. |
inputValue |
string |
Current uncommitted input text. |
state |
TagsInputState |
Stable root state token. |
add |
(text: string) => boolean |
Parse, validate, and append one tag from text. Reports whether it was added. |
addTag |
(tag: T) => boolean |
Validate and append one already-parsed tag. Reports whether it was added. |
remove |
(index: number) => boolean |
Remove the tag at index. Reports whether it was removed. |
clear |
() => boolean |
Remove every tag. Reports whether the value changed. |
setInputValue |
(text: string) => void |
Replace the uncommitted input text. |
focus |
(options?: FocusOptions) => void |
Move DOM focus to the text input. |
reset |
() => boolean |
Restore defaultValue and clear the input text. Reports whether tags changed. |
Source: src/families/form/tags-input/tags-input-input.vue
Props
| Prop |
Type |
Default |
Description |
placeholder |
string |
undefined |
Native placeholder shown while the input is empty. |
autocomplete |
string |
"off" |
Native autocomplete hint. |
Exposed
| Member |
Type |
Description |
element |
HTMLInputElement | null |
Rendered native text input. |
focus |
(options?: FocusOptions) => void |
Move DOM focus to the input. |
Source: src/families/form/tags-input/tags-input-item.vue
Props
| Prop |
Type |
Default |
Description |
value (required) |
T |
required |
Tag value rendered by this item, normally one entry of the root slot tags. |
index (required) |
number |
required |
Zero-based position of value in the root tag list. |
disabled |
boolean |
false |
Keep this tag visible but unfocusable, uneditable, and unremovable. |
editLabel |
string |
"Edit tag" |
Accessible name prefix of the inline edit input. |
Slots
| Slot |
Slot props |
Description |
default |
TagsInputItemSlotState<T> |
Tag content, normally TagsInputItemText and TagsInputItemDelete. Hidden while editing. |
Exposed
| Member |
Type |
Description |
element |
HTMLSpanElement | null |
Rendered tag element. |
focus |
(options?: FocusOptions) => void |
Move DOM focus to this tag. |
remove |
() => boolean |
Remove this tag. Reports whether it was removed. |
edit |
() => boolean |
Enter inline editing when the root is editable. Reports whether editing started. |
Source: src/families/form/tags-input/tags-input-item-delete.vue
Props
| Prop |
Type |
Default |
Description |
label |
string |
"Remove" |
Accessible name prefix followed by the tag text, e.g. "Remove vue". |
ariaLabel |
string |
undefined |
Full accessible name override. |
Slots
| Slot |
Slot props |
Description |
default |
TagsInputItemSlotState<unknown> |
Button content such as an icon. Receives the owning item state. |
TagsInputItemText
Source: src/families/form/tags-input/tags-input-item-text.vue
Slots
| Slot |
Slot props |
Description |
default |
TagsInputItemSlotState<unknown> |
Custom tag text. Receives the owning item state; defaults to the resolved tag text. |
Behavior
Contract
TagsInput is a headless compound field that turns typed or pasted text into a
typed tag list. tags-input-root.vue (generic="T") owns the value, parsing,
validation, duplicate detection, the max limit, native form association, and
form reset. tags-input-item.vue renders one focusable tag, handles roving
arrow-key focus, deletion, and inline editing. tags-input-item-text.vue
renders the resolved tag text, tags-input-item-delete.vue renders a
non-tabbable remove button, and tags-input-input.vue renders the native text
input that commits candidates.
The text input is the single Tab stop. Tags are tabindex="-1" elements with
role="group", aria-roledescription="tag", and an accessible name equal to
the tag text; group is used because each tag contains its own remove button,
which a button or option role would not permit. Styling is consumer-owned
through parts, slots, CSS, and data attributes.
Without parseTag, tags are the trimmed typed strings; non-string tag types
must provide parseTag (and usually tagText, which also produces each
submitted form value).
Public Surface
| Surface |
Contract |
TagsInputRoot props |
id, modelValue, defaultValue, parseTag, tagText, by, validate, delimiters, addOnPaste, addOnBlur, allowDuplicates, max, editable, disabled, readonly, required, name, form, dir, aria props |
TagsInputRoot emits |
update:modelValue(tags), add(tag, index, source), remove(tag, index, source), edit(tag, previous, index), invalid(event) |
TagsInputRoot slots |
default(state) with tags, inputValue, count, full, disabled, readonly, invalid, state |
TagsInputRoot expose |
element, id, tags, inputValue, state, add, addTag, remove, clear, setInputValue, focus, reset |
TagsInputItem props / slots |
value, index, disabled, editLabel; default(state) |
TagsInputItem expose |
element, focus, remove, edit |
TagsInputItemText |
default(state) slot; falls back to the resolved tag text |
TagsInputItemDelete |
label (prefix, default "Remove"), ariaLabel override; default(state) slot |
TagsInputInput |
placeholder, autocomplete; expose element, focus |
| Parts |
root, item, item-text, item-delete, item-edit, input |
| Data attributes |
root data-vize-ui="tags-input", data-state, data-count, data-full, data-invalid, data-disabled, data-readonly; item data-state, data-index, data-active, data-disabled |
Normative Behavior
| # |
State |
Input |
Outcome |
Proving test |
| T1 |
any |
render |
tags, ids (<id>-tag-<index>), aria wiring, remove names, and data attributes are published |
renders tags, input semantics, ids, and data attributes |
| T2 |
input has text |
Enter |
trimmed text is parsed and appended; default prevented; input clears; add(..., "enter") |
Enter and delimiter keys commit trimmed text while empty Enter passes through |
| T3 |
input empty |
Enter |
not prevented, so native implicit form submission still works |
Enter and delimiter keys commit trimmed text while empty Enter passes through |
| T4 |
any |
delimiter key |
default prevented and the text commits with source "delimiter" |
Enter and delimiter keys commit trimmed text while empty Enter passes through |
| T5 |
any |
input event containing delimiters |
complete segments commit; the trailing fragment stays in the input |
typed delimiters commit complete segments and keep the trailing fragment |
| T6 |
addOnPaste |
paste with delimiters, line breaks, or tabs |
default prevented; every segment commits with source "paste"; undelimited paste stays native |
pasting delimited or multi-line text splits it into tags |
| T7 |
candidate rejected |
commit |
invalid emits parse, duplicate, max, or invalid (+ validator message); rejected text stays editable |
duplicates, max, parse failures, and validators emit invalid events |
| T8 |
by / allowDuplicates |
commit equal tag |
a property name or comparator defines equality; allowDuplicates accepts equals |
allowDuplicates and by keys control duplicate detection |
| T9 |
caret at start, tags |
Backspace / ArrowLeft in input |
focus moves to the last tag (first Backspace focuses, the next one deletes) |
keyboard matrix moves focus between tags and removes with Backspace and Delete |
| T10 |
text before caret |
Backspace / ArrowLeft in input |
native caret editing; focus stays in the input |
Backspace and ArrowLeft keep caret editing when text precedes the caret |
| T11 |
tag focused |
ArrowLeft / ArrowRight / Home / End |
previous / next tag (no wrap), next past the last tag and End focus the input, Home the first tag |
keyboard matrix moves focus between tags and removes with Backspace and Delete |
| T12 |
tag focused |
Backspace / Delete |
removes the tag; Backspace focuses the previous tag, Delete the tag now in the slot, else the input |
keyboard matrix moves focus between tags and removes with Backspace and Delete |
| T13 |
dir="rtl" |
arrows |
horizontal arrows are mirrored |
rtl direction mirrors horizontal arrows |
| T14 |
any |
remove button click |
removes with source "delete-button"; focus stays on a tag or returns to the input |
the delete button removes its tag and keeps focus in the field |
| T15 |
editable |
Enter / F2 / double-click on tag |
inline edit input opens focused; Enter commits (edit), invalid keeps editing, Escape cancels, blur commits |
editable tags edit inline with Enter, F2, and double-click |
| T16 |
disabled / readonly |
any change |
disabled removes tags from focus and submission; readonly keeps them focusable and submitted; nothing changes |
disabled and readonly fields block every change |
| T17 |
name in a form |
submit / reset |
one hidden entry per tag (tagText); required blocks empty submission; form reset restores defaultValue |
submits one form entry per tag, validates required, and restores on form reset |
| T18 |
controlled |
commit |
emits the requested list and waits for the parent |
controlled tags wait for the parent to accept updates |
| T19 |
addOnBlur / composition |
blur / IME Enter or delimiter |
blur commits only with addOnBlur; keys during composition never commit |
addOnBlur commits on blur and IME composition never commits |
| T20 |
any |
pointerdown on empty root space |
default prevented and focus moves to the input |
pointerdown on empty root space focuses the input |
| T21 |
any |
exposed API |
add, addTag, remove, clear, setInputValue, focus, reset, item focus/edit share one state |
exposed root and item methods share state |
| T22 |
no provider |
mount part alone |
throws VIZE_UI_CONTEXT_MISSING |
items and parts require a matching TagsInput provider |
| T23 |
SSR |
render twice / hydrate |
byte-identical markup; hydration keeps nodes and ids with no diagnostics |
renders byte-identical TagsInput markup across isolated SSR requests, hydrates tags, ids, and hidden inputs without replacing SSR nodes |