Component Library

← Back to gallery

List group with actions

Category: Tables & lists · Tags: list, actions, rows, edit, remove

Team projects

  • Website redesign

    6 members, updated 2 days ago

  • Mobile onboarding

    3 members, updated today

  • Pricing page test

    2 members, updated last week

How to use it

Use for a short list of records the user manages, such as projects, saved addresses or API keys, where each row has two or three actions. Do not use it for data people compare across columns (use a table) or when rows have many actions (move them into a context menu). After copying, keep each hidden name in sync with its title, and connect Edit and Remove to your own handlers, adding a confirmation before a permanent delete.

Accessibility

A heading names the <ul> through aria-labelledby, and each row's Edit and Remove buttons are native <button>s whose accessible names include the row title from visually hidden text ("Edit Website redesign"), so a list of buttons read out of context still makes sense. Removing a row announces it in a role="status" line and moves focus to the next row's first button, or to the heading when the list empties, so keyboard focus is never lost. Titles are #0f172a on #ffffff (17.85:1), meta text #475569 on #ffffff (7.58:1), Edit #0f172a on #ffffff (#f1f5f9 on hover, 16.30:1) and Remove #b91c1c on #ffffff (6.47:1).

Source (copy-ready)

<div class="dcl-list-group-actions">
  <h2 class="dcl-list-group-actions__heading" id="dcl-list-group-actions-heading" tabindex="-1">Team projects</h2>
  <ul class="dcl-list-group-actions__list" aria-labelledby="dcl-list-group-actions-heading">
    <li class="dcl-list-group-actions__row">
      <div class="dcl-list-group-actions__main"><p class="dcl-list-group-actions__title">Website redesign</p><p class="dcl-list-group-actions__meta">6 members, updated 2 days ago</p></div>
      <div class="dcl-list-group-actions__actions"><button type="button" class="dcl-list-group-actions__edit">Edit<span class="dcl-list-group-actions__sr"> Website redesign</span></button><button type="button" class="dcl-list-group-actions__remove">Remove<span class="dcl-list-group-actions__sr"> Website redesign</span></button></div>
    </li>
    <li class="dcl-list-group-actions__row">
      <div class="dcl-list-group-actions__main"><p class="dcl-list-group-actions__title">Mobile onboarding</p><p class="dcl-list-group-actions__meta">3 members, updated today</p></div>
      <div class="dcl-list-group-actions__actions"><button type="button" class="dcl-list-group-actions__edit">Edit<span class="dcl-list-group-actions__sr"> Mobile onboarding</span></button><button type="button" class="dcl-list-group-actions__remove">Remove<span class="dcl-list-group-actions__sr"> Mobile onboarding</span></button></div>
    </li>
    <li class="dcl-list-group-actions__row">
      <div class="dcl-list-group-actions__main"><p class="dcl-list-group-actions__title">Pricing page test</p><p class="dcl-list-group-actions__meta">2 members, updated last week</p></div>
      <div class="dcl-list-group-actions__actions"><button type="button" class="dcl-list-group-actions__edit">Edit<span class="dcl-list-group-actions__sr"> Pricing page test</span></button><button type="button" class="dcl-list-group-actions__remove">Remove<span class="dcl-list-group-actions__sr"> Pricing page test</span></button></div>
    </li>
  </ul>
  <p class="dcl-list-group-actions__empty" hidden>No projects left.</p>
  <p class="dcl-list-group-actions__status" role="status"></p>
</div>
<style>
.dcl-list-group-actions { max-width: 32rem; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-list-group-actions__heading { margin: 0 0 0.5rem; font-size: 1.15rem; color: #0f172a; background-color: #ffffff; }
.dcl-list-group-actions__heading:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; }
.dcl-list-group-actions__list { margin: 0; padding: 0; list-style: none; border: 1px solid #cbd5e1; border-radius: 8px; }
.dcl-list-group-actions__row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.5rem 1rem; padding: 0.75rem 1rem; border-bottom: 1px solid #e2e8f0; }
.dcl-list-group-actions__main { flex: 1 1 12rem; min-width: 0; }
.dcl-list-group-actions__title { margin: 0; font-weight: 600; overflow-wrap: anywhere; color: #0f172a; background-color: #ffffff; }
.dcl-list-group-actions__meta { margin: 0.15rem 0 0; font-size: 0.875rem; color: #475569; background-color: #ffffff; }
.dcl-list-group-actions__actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.dcl-list-group-actions__edit, .dcl-list-group-actions__remove { font: inherit; font-size: 0.9rem; padding: 0.4rem 0.8rem; border: 1px solid #94a3b8; border-radius: 6px; cursor: pointer; color: #0f172a; background-color: #ffffff; }
.dcl-list-group-actions__edit:hover { color: #0f172a; background-color: #f1f5f9; }
.dcl-list-group-actions__remove { border-color: #b91c1c; color: #b91c1c; background-color: #ffffff; }
.dcl-list-group-actions__remove:hover { color: #ffffff; background-color: #b91c1c; }
.dcl-list-group-actions__edit:focus-visible, .dcl-list-group-actions__remove:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; }
.dcl-list-group-actions__sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
.dcl-list-group-actions__empty { margin: 0.5rem 0 0; color: #475569; background-color: #ffffff; }
.dcl-list-group-actions__status { min-height: 1.5em; margin: 0.5rem 0 0; font-size: 0.9rem; color: #334155; background-color: #ffffff; }
</style>
<script>
(function () {
  document.querySelectorAll(".dcl-list-group-actions").forEach(function (root) {
    if (root.dataset.dclBound) { return; }
    root.dataset.dclBound = "true";
    var list = root.querySelector(".dcl-list-group-actions__list");
    var status = root.querySelector(".dcl-list-group-actions__status");
    var empty = root.querySelector(".dcl-list-group-actions__empty");
    var heading = root.querySelector(".dcl-list-group-actions__heading");
    if (!list) { return; }
    function titleOf(row) {
      var title = row.querySelector(".dcl-list-group-actions__title");
      return title ? title.textContent : "Item";
    }
    function say(text) { if (status) { status.textContent = text; } }
    list.addEventListener("click", function (event) {
      var button = event.target.closest("button");
      var row = button ? button.closest(".dcl-list-group-actions__row") : null;
      if (!row || !list.contains(row)) { return; }
      if (button.classList.contains("dcl-list-group-actions__edit")) {
        say("Editing " + titleOf(row) + ".");
        return;
      }
      if (button.classList.contains("dcl-list-group-actions__remove")) {
        var next = row.nextElementSibling || row.previousElementSibling;
        var name = titleOf(row);
        row.remove();
        say(name + " removed.");
        var target = next ? next.querySelector("button") : null;
        if (target) { target.focus(); }
        else {
          if (empty) { empty.hidden = false; }
          list.hidden = true;
          if (heading) { heading.focus(); }
        }
      }
    });
  });
})();
</script>

Was this useful? Send feedback.