Field
Text entry in a recess, the label printed above it, and a hint that says how to fix an error.
Kit part · Badges & tags · Inputs
A text input that turns what you type into printed tags. Enter or a comma sets one down in the well; each tag is a key that takes itself away, and every change is spoken.
Both finishes, side by side. Hover, pressed and focus are forced with .is-hover, .is-down and .is-focus; checked and disabled are real.
Aluminium
| Part | Rest | Tag hover | Tag pressed | Tag focus | Typing | Disabled |
|---|---|---|---|---|---|---|
| Tag input |
Graphite
| Part | Rest | Tag hover | Tag pressed | Tag focus | Typing | Disabled |
|---|---|---|---|---|---|---|
| Tag input |
| Name | Kind | Values | What it does |
|---|---|---|---|
| <tui-tag-input> | element | name · value · max | The well. name: what each tag posts under. value: the starting tags, comma separated. max: how many at most. |
| disabled | attribute | — | Stops typing and removing; 45%. |
| input.tui-tag-input__entry | element | inside | Where you type. Give it an id and a label, a placeholder, a maxlength for one tag and an aria-describedby for the hint. |
| .tui-tag-input__tag | class | made by script | A tag: a small raised key with a cross, named “Remove …”. Not a tab stop; reached by arrow. |
| .tui-tag-input__status | class | made by script | The polite live region: “Added print. 3 tags.” |
| values | property | array of strings | Read or set the list. |
| change | event | detail.values | After tags are added or removed. Bubbles. The inner input’s own change is not passed on. |
| tagSplit(text) · tagAdd(tags, incoming, { max }) · tagSays(…) | function | kit.mjs | The rules, pure: split a pasted list, refuse duplicates (case aside) and overflow, and say what happened. |
| .tui-field[data-error] | attribute | on the field | Rings the well in red, as it does any input. |
| Enter · , | keyboard | in the input | Add the tag. Enter with nothing typed submits the form as usual. |
| Backspace · ← | keyboard | in the input | Backspace on an empty input removes the last tag; ← at the start moves to the last tag. |
| ← → · Home · End · Backspace · Delete | keyboard | on a tag | Move between tags and back to the input; remove the one you are on. |
<div class="tui-field">
<label class="tui-label" for="topics">Topics</label>
<tui-tag-input name="topics" value="print, aluminium" max="8">
<input class="tui-tag-input__entry" id="topics" placeholder="Add a topic"
autocomplete="off" aria-describedby="topics-hint">
</tui-tag-input>
<span class="tui-hint" id="topics-hint">Enter or a comma adds one. Backspace takes the last away.</span>
</div>import '/kit/kit.mjs'; // defines <tui-tag-input>
const tags = document.querySelector('tui-tag-input[name="topics"]');
tags.values; // ['print', 'aluminium']
tags.values = ['print', 'metal']; // set the list
tags.addEventListener('change', (e) => save(e.detail.values));
// In a form, each tag posts as its own value:
new FormData(form).getAll('topics'); // ['print', 'metal']Needs kit.css and kit.mjs. How to install the kit.
<label>, and the element is a role="group" named by it, so the tags and the input read as one field.A well like the combo’s, whose tags are the kit’s keys scaled to fit it: 30px caps at 7px corners inside 4px of well, so the well’s 11px corner is concentric with theirs. The element builds its tags from a list, and a list is all it holds: tagAdd refuses a repeat (case aside) and anything past max, tagSays writes what to announce, and both are pure and tested. A hidden input per tag, all under the element’s name, is how a form receives them. A pasted list is split on commas, semicolons and line breaks. Leaving the field commits what is typed, so nothing is lost silently.
For values people make up: topics, recipients, labels. A fixed list is a select.
6 rules in this file, with the foundations they rest on and the anti-patterns that break them. Full rules ship in the brain with the Pass.