ExklusivBeta
Tag Input <mv-tag-input>
Chip-Eingabe für Labels, Empfänger, Skills und Schlagwörter: Enter, Komma oder Tab fügt hinzu, das Einfügen von „a, b; c“ (oder einer aus einer Tabelle kopierten Spalte) fügt jeden Eintrag einzeln hinzu, Backspace markiert den letzten Chip und entfernt ihn dann, die Pfeiltasten wandern durch die Chips, optional pattern, Limit, Deduplizierung ohne Beachtung von Groß-/Kleinschreibung und Akzenten, die den vorhandenen Chip aufblitzen lässt, und native <datalist>-Vorschläge. Was andere übersehen: Es ist ein echtes Formularfeld (ElementInternals: wiederholte name-Einträge oder ein per Trennzeichen verbundener Wert, Reset, Wiederherstellung bei Zurück/Vor, Validität für required und pattern), getippter Text geht nie verloren (bei blur oder submit wird er zum Tag oder bleibt stehen; überzähliger und per Veto abgelehnter Text wandert zurück ins Feld), Trennzeichen werden auch von Mobil- und IME-Tastaturen sowie in arabischer und CJK-Zeichensetzung erkannt, und Ansagen bleiben kurz („research removed, 2 tags“).
| Kategorie | Formulare |
|---|---|
| Typ | Web Component (<mv-tag-input>) |
| Status | Beta |
| Keywords | exclusive, light, tags, chips, tokens, multi-value, recipients, keywords, form-associated, paste |
When to use
- A form collects several free-form values such as tags, keywords, skills or email recipients and must submit them
- Users paste lists from a spreadsheet, an email client or a document and expect each entry to become a tag
- Entries must match a format such as email addresses, with invalid ones kept and flagged rather than dropped
- A filter bar or settings page needs removable keyword chips that work fully from the keyboard
Avoid when
- Values must be picked from a long or remote list with filtering, groups and option rendering → use Combobox instead
- The field is a storefront search with recent searches and product results → use Search Autocomplete instead
- Only a fixed handful of options can be chosen, with nothing typed → use Checkbox instead
Installation
node scripts/add.mjs tag-input --out ./src/marvelousKI-Agent mit dem MCP-Server von Marvelous UI: install_components({ slugs: ["tag-input"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Kopierte Dateien (inklusive Abhängigkeiten): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, components/tag-input/tag-input.js, components/tag-input/tag-input.css.
Verwendung
Schnellstart mit dem kleinsten funktionierenden Markup:
<mv-tag-input name="tags" value="design, research"></mv-tag-input>Referenz-Markup zum Starten und Anpassen mit Attributen, data-* und CSS-Variablen:
<form id="mv-tag-input-demo" style="display:grid;gap:1.1rem;width:min(100%,34rem);padding:1.25rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);background:var(--mv-surface);box-shadow:var(--mv-shadow-sm)">
<div style="display:grid;gap:.4rem">
<label for="mv-tag-demo-topics" style="font-size:.875rem;font-weight:600">Topics</label>
<mv-tag-input id="mv-tag-demo-topics" name="topics" value="design, research" unique max="6" required placeholder="Add a topic…">
<datalist>
<option value="Accessibility"></option>
<option value="Branding"></option>
<option value="Café culture"></option>
<option value="Localization"></option>
<option value="Pricing"></option>
<option value="Typography"></option>
</datalist>
</mv-tag-input>
<small style="color:var(--mv-fg-muted);font-size:.8125rem">Up to 6. Paste “ux, pricing; Café culture” to add several; typing “Design” flashes the existing tag.</small>
</div>
<div style="display:grid;gap:.4rem">
<label for="mv-tag-demo-invite" style="font-size:.875rem;font-weight:600">Invite reviewers</label>
<mv-tag-input id="mv-tag-demo-invite" name="reviewers" separator=";" unique pattern="[^\s@]+@[^\s@]+\.[^\s@]{2,}" placeholder="[email protected]"
value="[email protected]; [email protected]; lucia.fernandez@lumen"></mv-tag-input>
<small style="color:var(--mv-fg-muted);font-size:.8125rem">Emails only, sent as one “;”-separated value. Invalid addresses stay, flagged, so nothing is lost.</small>
</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:.5rem">
<button class="mv-button" type="submit">Save project</button>
<button class="mv-button" data-variant="ghost" type="reset">Reset</button>
<output id="mv-tag-demo-out" style="flex-basis:100%;min-height:1.2em;font:.75rem/1.5 var(--mv-font-mono);color:var(--mv-fg-muted);overflow-wrap:anywhere"></output>
</div>
</form>
<script type="module">
const form = document.getElementById("mv-tag-input-demo");
const out = document.getElementById("mv-tag-demo-out");
form.addEventListener("submit", (e) => {
e.preventDefault();
const fd = new FormData(form);
out.textContent = [...fd].map(([k, v]) => `${k}=${v}`).join(" · ");
});
form.addEventListener("invalid", () => { out.textContent = ""; }, true);
</script>API
Attributes
| Name | Typ | Default | Description |
|---|---|---|---|
name | string | Form field name. Without separator, each tag is submitted as its own name entry (like a multiple select). | |
value | string | Initial tags, split on commas, semicolons, line breaks, tabs (and separator); also the value restored by form reset. | |
separator | string | Submit one value with the tags joined by this string (e.g. “,” or “;”) instead of repeated entries; it also splits typed and pasted text. | |
max | number | Maximum number of tags; extra text stays in the field and “Limit reached” is announced. | |
pattern | regex | Each tag must fully match (like input pattern). Mismatches are still added, flagged visually and as “(invalid)”, and make the field invalid (patternMismatch). | |
unique | boolean | No duplicates, compared without case or accents (“cafe” = “Café”); the existing chip flashes instead. | |
pending | commit | keep | commit | What happens to typed text when focus leaves or the form submits: it becomes a tag (commit) or stays in the field (keep). |
placeholder | string | Add a tag… | Shown while there are no tags (defaults to strings.placeholder). |
required / disabled | boolean | Standard form behavior: valueMissing with no tags; disabled (also from a disabled fieldset) makes the field and chips inert. | |
data-count / data-full / data-invalid | set by the component | Tag count, limit reached, at least one invalid tag (for styling). |
Properties
| Name | Typ | Description |
|---|---|---|
tags | string[] | Current tags (a copy). Setting it replaces them silently, like a native field value. Can be set before the element is defined. |
value | string | Tags joined with separator (default “, ”); setting accepts a string or an array. |
suggestions | string[] | Suggestions shown in the native datalist popup (already added ones are hidden); picking one adds it. A <datalist> child works too. |
strings | object | Overrides for every visible and announced text: placeholder, hint, remove (“Remove {tag}”), invalidTag, added, addedInvalid, addedMany, removed, duplicate, full, required, mismatch, overflow. Plural entries are objects keyed by Intl.PluralRules category ({ zero?, one, other… }); numbers are formatted with Intl in the nearest lang. |
form / labels / validity / validationMessage / willValidate | read-only | Standard form-control properties. |
Methods
| Name | Description |
|---|---|
add(text | string[]) | Adds tags as the user would (split, dedupe, limit, mv-add veto, announcement); returns the tags added. |
remove(tag | index) | Removes a tag by value (case- and accent-insensitive) or index. |
clear() | Removes every tag (silent). |
checkValidity() / reportValidity() | Standard; the browser bubble points at the text field. |
focus() | Focuses the text field. |
Events
| Name | Description |
|---|---|
mv-add | Cancelable, before a user-added tag is created; detail = { value }. Cancel to veto (the text returns to the field). |
mv-remove | Cancelable, before a tag is removed; detail = { value, index }. |
mv-change | After tags were added or removed by the user; detail = { tags, value }. A native change event fires on the element too. |
Content structure
| Name | Description |
|---|---|
datalist | Optional <datalist> child with <option value> suggestions. |
CSS classes
| Name | Description |
|---|---|
mv-tag-input-list | The <ul role=list> of chips (display: contents, so chips and field share one wrapping row). |
mv-tag-input-chip / -text / -remove | A chip (li; data-invalid, data-flash), its label and its remove button. |
mv-tag-input-field | The text input. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-tag-input-chip-bg | var(--mv-bg-muted) | Chip background. |
--mv-tag-input-chip-fg | var(--mv-fg) | Chip text. |
--mv-tag-input-flash | var(--mv-accent) | Highlight of the existing chip when a duplicate is typed. |
--mv-tag-input-invalid | var(--mv-danger) | Invalid chip and field color. |
--mv-input-radius / --mv-input-bg | var(--mv-radius-md) / field background | Shared with mv-input, so both fields match. |
Accessibility
The host is a form-associated custom element: a <label for> names it (the inner text field is aria-labelledby the label, or takes the host’s aria-label), clicking the label or the empty part of the box focuses the field, and validity messages anchor to the field. Chips are a real list (ul role=list), so screen readers hear “list, 2 items”; each chip has a real button named “Remove design” (the × icon is aria-hidden). Only the text field is in the Tab order: Left arrow at the start of the field, or Backspace in an empty field, moves focus to the last chip’s remove button; Left/Right (mirrored in RTL), Home and End walk the chips, Right past the last one returns to the field; Backspace removes and moves left, Delete and Enter remove and stay in place, Escape or typing a character goes back to the field. A short screen-reader hint on the field says Enter or comma adds and Left arrow reaches the tags. One polite status region announces only results, briefly: “design added”, “3 tags added”, “research removed, 2 tags”, “Design is already added”, “Limit reached: 6 tags”. Invalid chips are flagged by a “!” mark, a wavy underline and the text “(invalid)”, not by color alone, and the field gets aria-invalid. Focus is always visible (the whole chip is outlined when its button has focus); forced colors use system outlines, a dashed border for invalid chips and a CanvasText mark. Chip entry and the duplicate flash scale only with --mv-motion and stop under prefers-reduced-motion or [data-motion=reduce] (the highlight still shows, statically). Enter never submits while text is pending; with an empty field it submits the form as usual. IME composition is respected.