GitHub

Components / Form & Input / Tag Input

Tag Input beta

An input that turns words into chips — Enter commits, Backspace repents.

Preview
Design Vanilla

Uses: Chip — include its CSS alongside this one.

<div class="k-tags" data-tags data-x="<svg width=&quot;11&quot; height=&quot;11&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot;><path d=&quot;M6 6l12 12M18 6 6 18&quot;/></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