Component Library

← Back to gallery

Selectable list

Category: Tables & lists · Tags: list, checkbox, select-all, indeterminate, bulk

Files to export

1 of 4 selected

How to use it

Use when people pick several items from a short list before a bulk action, such as exporting or deleting files (connect your own bulk action button; none is included). Do not use it for choosing a single option (use radio buttons) or for very long lists without search or paging. After copying, give each checkbox a unique id matched by its label's for, and connect your bulk action button to the checked values.

Accessibility

Every row is a native checkbox with its own <label>, the group is a <fieldset> with a <legend>, and the "Select all" checkbox sets the DOM indeterminate state when only some rows are checked, which screen readers announce as "mixed" or "partially checked". A polite aria-live line reports "2 of 4 selected" after each change, and checkboxes show a 2px #1d4ed8 outline on keyboard focus. Row text is #0f172a on #ffffff (17.85:1) or #0f172a on #eff6ff when selected (16.40:1), and file sizes are #475569 on #ffffff (7.58:1) or on #eff6ff (6.96:1).

Source (copy-ready)

<fieldset class="dcl-selectable-list">
  <legend class="dcl-selectable-list__legend">Files to export</legend>
  <div class="dcl-selectable-list__all">
    <input type="checkbox" id="dcl-selectable-list-all" class="dcl-selectable-list__allbox" aria-controls="dcl-selectable-list-items">
    <label for="dcl-selectable-list-all" class="dcl-selectable-list__alllabel">Select all</label>
  </div>
  <ul class="dcl-selectable-list__items" id="dcl-selectable-list-items">
    <li class="dcl-selectable-list__item dcl-selectable-list__item--selected"><input type="checkbox" id="dcl-selectable-list-f1" class="dcl-selectable-list__box" name="files" value="invoices-march.csv" checked><label for="dcl-selectable-list-f1" class="dcl-selectable-list__label">invoices-march.csv <span class="dcl-selectable-list__meta">84 KB</span></label></li>
    <li class="dcl-selectable-list__item"><input type="checkbox" id="dcl-selectable-list-f2" class="dcl-selectable-list__box" name="files" value="customers.csv"><label for="dcl-selectable-list-f2" class="dcl-selectable-list__label">customers.csv <span class="dcl-selectable-list__meta">1.2 MB</span></label></li>
    <li class="dcl-selectable-list__item"><input type="checkbox" id="dcl-selectable-list-f3" class="dcl-selectable-list__box" name="files" value="refunds-q1.csv"><label for="dcl-selectable-list-f3" class="dcl-selectable-list__label">refunds-q1.csv <span class="dcl-selectable-list__meta">36 KB</span></label></li>
    <li class="dcl-selectable-list__item"><input type="checkbox" id="dcl-selectable-list-f4" class="dcl-selectable-list__box" name="files" value="products.csv"><label for="dcl-selectable-list-f4" class="dcl-selectable-list__label">products.csv <span class="dcl-selectable-list__meta">410 KB</span></label></li>
  </ul>
  <p class="dcl-selectable-list__count" aria-live="polite">1 of 4 selected</p>
</fieldset>
<style>
.dcl-selectable-list { max-width: 24rem; min-width: 0; margin: 0; padding: 0.75rem 1rem 1rem; border: 1px solid #cbd5e1; border-radius: 8px; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-selectable-list__legend { padding: 0 0.25rem; font-weight: 700; color: #0f172a; background-color: #ffffff; }
.dcl-selectable-list__all { display: flex; align-items: center; gap: 0.6rem; padding: 0.5rem 0.5rem 0.6rem; border-bottom: 1px solid #e2e8f0; }
.dcl-selectable-list__alllabel { font-weight: 600; color: #0f172a; background-color: #ffffff; }
.dcl-selectable-list__items { margin: 0; padding: 0; list-style: none; }
.dcl-selectable-list__item { display: flex; align-items: center; gap: 0.6rem; padding: 0.5rem; border-bottom: 1px solid #e2e8f0; color: #0f172a; background-color: #ffffff; }
.dcl-selectable-list__item--selected { color: #0f172a; background-color: #eff6ff; }
.dcl-selectable-list__label { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.25rem 0.75rem; flex: 1; min-width: 0; overflow-wrap: anywhere; cursor: pointer; }
.dcl-selectable-list__meta { font-size: 0.85rem; color: #475569; }
.dcl-selectable-list__box, .dcl-selectable-list__allbox { width: 1.1rem; height: 1.1rem; flex-shrink: 0; accent-color: #1d4ed8; }
.dcl-selectable-list__box:focus-visible, .dcl-selectable-list__allbox:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; }
.dcl-selectable-list__count { margin: 0.6rem 0 0; font-size: 0.9rem; color: #334155; background-color: #ffffff; }
</style>
<script>
(function () {
  document.querySelectorAll(".dcl-selectable-list").forEach(function (root) {
    if (root.dataset.dclBound) { return; }
    root.dataset.dclBound = "true";
    var all = root.querySelector(".dcl-selectable-list__allbox");
    var count = root.querySelector(".dcl-selectable-list__count");
    var boxes = Array.prototype.slice.call(root.querySelectorAll(".dcl-selectable-list__box"));
    if (!boxes.length) { return; }
    function update() {
      var checked = boxes.filter(function (box) { return box.checked; }).length;
      boxes.forEach(function (box) {
        var item = box.closest(".dcl-selectable-list__item");
        if (item) { item.classList.toggle("dcl-selectable-list__item--selected", box.checked); }
      });
      if (all) {
        all.checked = checked === boxes.length;
        all.indeterminate = checked > 0 && checked < boxes.length;
      }
      if (count) { count.textContent = checked + " of " + boxes.length + " selected"; }
    }
    boxes.forEach(function (box) { box.addEventListener("change", update); });
    if (all) {
      all.addEventListener("change", function () {
        boxes.forEach(function (box) { box.checked = all.checked; });
        update();
      });
    }
    update();
  });
})();
</script>

Was this useful? Send feedback.