Component Library

← Back to gallery

Tag list

Category: Data display · Tags: tags, chips, remove, labels, keywords

Article topics

  • Accessibility
  • CSS
  • Performance
  • Forms

How to use it

Use to show editable labels on an item -- topics on an article, skills on a profile -- where people may remove some. Each remove button's aria-label must name its own tag; the script reads the visible name for the announcement and binds once per list, so several lists on a page work. Pair it with a separate field for adding tags; for filters that toggle on and off, use checkboxes or toggle buttons instead. Give each copy its own heading id and update the section's aria-labelledby to match.

Accessibility

The tags are a <ul> under a heading, and each has a native <button> whose aria-label is "Remove tag Accessibility", so the visible × is never read alone. After a removal, focus moves to the next tag's remove button (or the previous one if it was last, or the heading once the list is empty) instead of being lost, and a visually hidden role="status" region announces "Removed tag Accessibility." Tag text #1e3a8a on #dbeafe is 8.5:1, also while the button is hovered (#bfdbfe, 7.3:1); focus shows a 2px #1d4ed8 outline.

Source (copy-ready)

<section class="dcl-taglist" aria-labelledby="dcl-taglist-title">
  <h3 class="dcl-taglist__title" id="dcl-taglist-title">Article topics</h3>
  <ul class="dcl-taglist__list" role="list">
    <li class="dcl-taglist__item"><span class="dcl-taglist__name">Accessibility</span><button type="button" class="dcl-taglist__remove" aria-label="Remove tag Accessibility">&times;</button></li>
    <li class="dcl-taglist__item"><span class="dcl-taglist__name">CSS</span><button type="button" class="dcl-taglist__remove" aria-label="Remove tag CSS">&times;</button></li>
    <li class="dcl-taglist__item"><span class="dcl-taglist__name">Performance</span><button type="button" class="dcl-taglist__remove" aria-label="Remove tag Performance">&times;</button></li>
    <li class="dcl-taglist__item"><span class="dcl-taglist__name">Forms</span><button type="button" class="dcl-taglist__remove" aria-label="Remove tag Forms">&times;</button></li>
  </ul>
  <p class="dcl-taglist__empty" hidden>No topics yet.</p>
  <p class="dcl-taglist__status" role="status"></p>
</section>
<style>
.dcl-taglist { max-width: 32rem; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-taglist__title { margin: 0 0 0.5rem; font-size: 1rem; color: #0f172a; background-color: #ffffff; }
.dcl-taglist__title:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; }
.dcl-taglist__list { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0; padding: 0; list-style: none; }
.dcl-taglist__item { display: inline-flex; align-items: center; gap: 0.125rem; max-width: 100%; padding: 0.125rem 0.125rem 0.125rem 0.625rem; border-radius: 999px; color: #1e3a8a; background-color: #dbeafe; }
.dcl-taglist__name { min-width: 0; overflow-wrap: anywhere; color: #1e3a8a; background-color: #dbeafe; }
.dcl-taglist__remove { display: inline-flex; align-items: center; justify-content: center; width: 1.75rem; height: 1.75rem; border: none; border-radius: 50%; font: inherit; font-size: 1.125rem; line-height: 1; cursor: pointer; color: #1e3a8a; background-color: #dbeafe; }
.dcl-taglist__remove:hover { color: #1e3a8a; background-color: #bfdbfe; }
.dcl-taglist__remove:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 1px; }
.dcl-taglist__empty { margin: 0; color: #475569; background-color: #ffffff; }
.dcl-taglist__status { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; color: #0f172a; background-color: #ffffff; }
</style>
<script>
(function () {
  document.querySelectorAll(".dcl-taglist").forEach(function (root) {
    if (root.dataset.dclBound) { return; }
    root.dataset.dclBound = "true";
    var list = root.querySelector(".dcl-taglist__list");
    var status = root.querySelector(".dcl-taglist__status");
    var heading = root.querySelector(".dcl-taglist__title");
    var empty = root.querySelector(".dcl-taglist__empty");
    if (!list) { return; }
    list.addEventListener("click", function (event) {
      var button = event.target.closest(".dcl-taglist__remove");
      if (!button || !list.contains(button)) { return; }
      var buttons = Array.prototype.slice.call(list.querySelectorAll(".dcl-taglist__remove"));
      var index = buttons.indexOf(button);
      var item = button.closest(".dcl-taglist__item");
      var label = item ? item.querySelector(".dcl-taglist__name") : null;
      var name = label ? label.textContent.trim() : "";
      if (item) { item.remove(); } else { button.remove(); }
      buttons.splice(index, 1);
      var next = buttons[index] || buttons[index - 1];
      if (status) { status.textContent = "Removed tag " + name + "."; }
      if (next) {
        next.focus();
      } else {
        if (empty) { empty.hidden = false; }
        if (heading) { heading.tabIndex = -1; heading.focus(); }
      }
    });
  });
})();
</script>

Was this useful? Send feedback.