Tag Entry
Type a value and press Enter or comma to commit it as a chip. Chips share the row with the input and wrap onto new rows as they accumulate.
<div class="nds-form-container nds-taginput" data-taginput-name="skills">
<div class="nds-form-header">
<label for="taginput-default-input"><span class="nds-label">Skills</span></label>
</div>
<div class="nds-form-control">
<input type="text" id="taginput-default-input" placeholder="Add a skill…">
</div>
<div class="nds-form-footer" data-feedback-target hidden></div>
</div>
Server-Rendered Restore
Emit one hidden input per saved tag and the field renders their chips on load. The field name is adopted from the hidden inputs, so no wrapper attribute is needed. This demo also shows the neutral chip variant via data-chip-class.
<div class="nds-form-container nds-taginput" data-chip-class="nds-neutral nds-sm">
<div class="nds-form-header">
<label for="taginput-restore-input"><span class="nds-label">Interests</span></label>
</div>
<div class="nds-form-control">
<!-- One hidden input per saved tag: seeds the chips and donates the field name -->
<input type="hidden" name="interests[]" value="Design">
<input type="hidden" name="interests[]" value="Development">
<input type="text" id="taginput-restore-input" placeholder="Add an interest…">
</div>
<div class="nds-form-footer" data-feedback-target hidden></div>
</div>
Tag Limit
Cap the number of tags with a wrapper attribute. Adds past the limit are rejected with an error in the field footer, clearing as soon as a tag is removed.
<div class="nds-form-container nds-taginput" data-taginput-name="topics" data-max-tags="3">
<div class="nds-form-header">
<label for="taginput-max-input"><span class="nds-label">Topics</span></label>
</div>
<div class="nds-form-control">
<input type="text" id="taginput-max-input" placeholder="Add up to 3 topics…">
</div>
<div class="nds-form-footer" data-feedback-target hidden></div>
</div>
Autocomplete Assist
Add data-url to the wrapper and the field surfaces server-backed suggestions as the user types. Picked suggestions commit as tags, free typing still works, so existing tags get reused instead of retyped. The inert spinner in the action slot shows while suggestions fetch. Add data-strict and only suggestions commit: the right shape for bounded vocabularies like assigning people or categories.
Built-in Features
Any .nds-taginput on the page wires up on load. Clicking anywhere on the field puts the caret in the input.
Enter or a comma commits the typed text, including the Arabic comma from Arabic keyboard layouts. Pasted text splits on separators into multiple tags, and leaving the field commits pending text instead of losing it.
Backspace on an empty input pops the last tag back into the input as editable text rather than deleting it, so a typo never means retyping the whole value.
Each tag ships as an <input type="hidden" name="field[]">, so a wrapping <form> posts the tags as an array. The same hidden inputs, server-rendered, restore a saved submission on load with no inline JS.
Add data-url to the wrapper and typing surfaces server-backed suggestions: picking one commits it as a tag, while free typing keeps working. Add data-strict to accept only suggestions, turning the field into a searchable picker for bounded vocabularies.
Duplicates (case-insensitive) and adds past data-max-tags are rejected with an error in the field footer, clearing on the next successful commit or removal.
Every add, removal, edit, and rejection announces through the shared NDS live region in both English and Arabic, and removing a chip hands keyboard focus to the next chip instead of dropping it.
Every field exposes an instance on the DOM node with getValues(), addTag(), removeTag(), and clear(). Listen for nds:taginput:change to react to changes.
Usage Guidelines
Best Practices
- Use tag input for open-ended, user-defined values: skills, keywords, topics, reference numbers. The vocabulary belongs to the user, not to a predefined list
- When the options are a fixed set the user picks from, use Multiselect instead: it validates against known values and supports grouped options
- For a single free-text value, use a plain text input. The chip ceremony adds friction when only one value is expected
- Name the field with
data-taginput-name, or let server-rendered hidden inputs donate the name. An unnamed field is UI-only and posts nothing - Set
data-max-tagswhen the backend caps the list. The limit is enforced at entry with visible feedback, so users never lose work at submit time - Keep expected tags short, one to three words. Long values wrap awkwardly as chips and are usually a sign the field should be a textarea
- Duplicates are rejected case-insensitively at entry. Normalize casing server-side if the stored values must be canonical
- When tag consistency matters, enable autocomplete assist with
data-url: suggestions steer users to existing tags instead of coining variants. See Autocomplete for the fetch options (data-fetch,data-min-chars,data-name) - When the tag count carries meaning (quota, pricing), listen for
nds:taginput:changeand show a counter outside the field rather than overloading the placeholder
Data Attributes
| Attribute | Description |
|---|---|
data-taginput-name | Set on .nds-taginput. Names the hidden carriers, posted as name[]. When omitted, the name is adopted from server-rendered hidden inputs; with neither, the selection does not post |
data-max-tags | Set on .nds-taginput. Caps the tag count; adds past the limit are rejected with footer feedback naming the limit |
data-chip-class | Set on .nds-taginput. Classes applied to generated chips: color variants nds-primary / nds-neutral, sizes nds-sm / nds-md / nds-lg, plus nds-rounded. Defaults to nds-primary nds-sm. See Chips |
data-url | Set on .nds-taginput to enable autocomplete assist (the input also needs autocomplete="on"). Picked suggestions commit as tags. The fetch behavior is configured with Autocomplete's own attributes |
data-strict | Set on .nds-taginput alongside data-url. Typed text never commits: Enter and comma show "choose from the suggestions" feedback, and only picked suggestions become tags. The programmatic addTag() API is not restricted |
data-taginput-value | Stamped on each chip by JS with the tag's value. Use it to target specific chips from consumer code or end-to-end tests |
data-state~="filled" | Stamped on .nds-taginput by JS when at least one tag exists. A styling hook for consumers; removed when the last tag is cleared |
JavaScript API
The NDS.TagInput namespace initializes all .nds-taginput fields on load. Each instance lives on its DOM node as element.ndsTagInput and exposes methods for programmatic control.