Selectable list
Category: Tables & lists · Tags: list, checkbox, select-all, indeterminate, bulk
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.