Components / Form & Input / Tag Input
Tag Input beta
An input that turns words into chips — Enter commits, Backspace repents.
Uses: Chip — include its CSS alongside this one.
<div class="k-tags" data-tags data-x="<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg>" style="max-width:430px"> <span class="k-chip" data-tag="design">Design<button class="k-chip-x" type="button" aria-label="Remove tag Design"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg></button></span> <span class="k-chip" data-tag="vanilla">Vanilla<button class="k-chip-x" type="button" aria-label="Remove tag Vanilla"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg></button></span> <input class="k-tags-in" type="text" placeholder="Add a tag…" aria-label="Add a tag"> </div>
.k-tags{display:flex;flex-wrap:wrap;gap:7px;align-items:center;border:1px solid var(--k-line);border-radius:var(--k-radius);background:var(--k-bg);padding:7px 9px;font-family:var(--k-font);transition:border-color .13s, box-shadow .13s;text-align:left}
.k-tags:focus-within{border-color:var(--k-accent);box-shadow:0 0 0 3px var(--k-accent-bg)}
.k-tags-in{flex:1;min-width:110px;border:none;outline:none;background:transparent;font:inherit;font-size:13.5px;color:var(--k-ink);height:29px}
.k-tags-in::placeholder{color:var(--k-ink-3)}
const tagsBox = document.querySelector('[data-tags]');
const tagsIn = tagsBox.querySelector('.k-tags-in');
const X_SVG = tagsBox.dataset.x;
function tagAdd(text) {
const val = text.trim().replace(/,$/, '');
if (!val) return;
const exists = [...tagsBox.querySelectorAll('.k-chip')].some(c => c.dataset.tag === val.toLowerCase());
if (exists) return;
const chip = document.createElement('span');
chip.className = 'k-chip';
chip.dataset.tag = val.toLowerCase();
chip.innerHTML = `<span></span><button class="k-chip-x" type="button" aria-label="Remove tag ${val}">${X_SVG}</button>`;
chip.querySelector('span').textContent = val;
chip.querySelector('.k-chip-x').addEventListener('click', () => chip.remove());
tagsBox.insertBefore(chip, tagsIn);
}
tagsIn.addEventListener('keydown', e => {
if (e.key === 'Enter' || e.key === ',') { e.preventDefault(); tagAdd(tagsIn.value); tagsIn.value = ''; }
if (e.key === 'Backspace' && !tagsIn.value) {
const chips = tagsBox.querySelectorAll('.k-chip');
if (chips.length) chips[chips.length - 1].remove();
}
});
tagsIn.addEventListener('blur', () => { tagAdd(tagsIn.value); tagsIn.value = ''; });
tagsBox.querySelectorAll('.k-chip-x').forEach(b => b.addEventListener('click', () => b.closest('.k-chip').remove()));
tagsBox.addEventListener('click', e => { if (e.target === tagsBox) tagsIn.focus(); });
Usage
For labels, skills, invite lists. Built on the Chip part, so committed tags look and behave exactly like chips everywhere else.
Accessibility
Chips are removed by real buttons. The input keeps a plain text value for the tag being typed, so nothing is lost if JavaScript is off — it degrades to a comma-separated field.
Related parts
Button stable
Black leads; lines and ghosts support. Hover, disabled and loading states included.
Checkbox / Radio stable
Native inputs with a restyled face. Keyboard support and form behavior stay intact.
Color Picker beta
A curated palette first, any color second — swatches for the 95% case, a native picker for the rest.
Combobox beta
Type to filter, arrows to choose — the Select for lists too long to scan.