UI Components Medium
Data List
A feature-rich data list component with search, column sorting, bulk selection, and inline status management. Designed for SaaS user/content management views.
Open in Lab
MCP
css vanilla-js
Targets: JS HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: #0f172a;
color: #f1f5f9;
min-height: 100vh;
display: flex;
align-items: flex-start;
justify-content: center;
padding: 2rem 1rem;
}
/* ── Container ── */
.dl-container {
width: min(960px, 100%);
display: flex;
flex-direction: column;
gap: 0;
background: #1e293b;
border: 1px solid rgba(255, 255, 255, 0.07);
border-radius: 14px;
overflow: hidden;
}
/* ── Header ── */
.dl-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 1.125rem 1.25rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.07);
flex-wrap: wrap;
}
.dl-header-left {
display: flex;
align-items: baseline;
gap: 0.625rem;
}
.dl-title {
font-size: 1rem;
font-weight: 700;
color: #f1f5f9;
}
.dl-count {
font-size: 0.75rem;
color: #64748b;
font-weight: 400;
}
.dl-header-right {
display: flex;
align-items: center;
gap: 0.75rem;
flex-wrap: wrap;
}
.dl-search {
display: flex;
align-items: center;
gap: 0.5rem;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 8px;
padding: 0.4375rem 0.75rem;
}
.dl-search input {
background: transparent;
border: none;
outline: none;
color: #f1f5f9;
font-size: 0.8125rem;
width: 220px;
}
.dl-search input::placeholder {
color: #475569;
}
.btn-add {
display: flex;
align-items: center;
gap: 0.375rem;
background: #38bdf8;
color: #0f172a;
border: none;
border-radius: 8px;
padding: 0.4375rem 0.875rem;
font-size: 0.8125rem;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
transition: background 0.15s;
}
.btn-add:hover {
background: #7dd3fc;
}
/* ── Bulk Bar ── */
.bulk-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0.625rem 1.25rem;
background: rgba(56, 189, 248, 0.07);
border-bottom: 1px solid rgba(56, 189, 248, 0.15);
max-height: 0;
overflow: hidden;
opacity: 0;
transition: max-height 0.25s ease, opacity 0.2s, padding 0.25s;
padding-top: 0;
padding-bottom: 0;
}
.bulk-bar.visible {
max-height: 60px;
opacity: 1;
padding: 0.625rem 1.25rem;
}
.bulk-count {
font-size: 0.8125rem;
font-weight: 600;
color: #38bdf8;
}
.bulk-actions {
display: flex;
gap: 0.5rem;
}
.bulk-btn {
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.1);
color: #cbd5e1;
border-radius: 6px;
padding: 0.3125rem 0.75rem;
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
transition: background 0.15s;
}
.bulk-btn:hover {
background: rgba(255, 255, 255, 0.1);
}
.bulk-btn--warn {
color: #fbbf24;
border-color: rgba(251, 191, 36, 0.2);
}
.bulk-btn--warn:hover {
background: rgba(251, 191, 36, 0.08);
}
.bulk-btn--danger {
color: #f87171;
border-color: rgba(239, 68, 68, 0.2);
}
.bulk-btn--danger:hover {
background: rgba(239, 68, 68, 0.08);
}
/* ── List wrapper ── */
.dl-list-wrapper {
display: flex;
flex-direction: column;
}
/* ── Rows ── */
.dl-row {
display: grid;
grid-template-columns: 40px 1fr 110px 110px 120px 70px;
align-items: center;
gap: 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.dl-row:last-of-type {
border-bottom: none;
}
.dl-row--header {
background: rgba(255, 255, 255, 0.02);
border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}
.dl-row--header .dl-col {
font-size: 0.72rem;
font-weight: 600;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.dl-row--data {
transition: background 0.12s;
}
.dl-row--data:hover {
background: rgba(255, 255, 255, 0.025);
}
.dl-row--data.selected {
background: rgba(56, 189, 248, 0.05);
}
.dl-row--data.removing {
opacity: 0;
transform: translateX(12px);
transition: opacity 0.28s, transform 0.28s;
}
/* ── Columns ── */
.dl-col {
padding: 0.75rem 0.875rem;
display: flex;
align-items: center;
min-width: 0;
}
.dl-col--check {
padding: 0.75rem 0.5rem 0.75rem 1rem;
justify-content: center;
}
.dl-col--name {
gap: 0.75rem;
}
.dl-col--actions {
justify-content: center;
position: relative;
}
/* Sortable headers */
.sortable {
cursor: pointer;
user-select: none;
gap: 0.375rem;
}
.sortable:hover {
color: #e2e8f0;
}
.sort-arrow {
opacity: 0.3;
transition: transform 0.2s, opacity 0.2s;
flex-shrink: 0;
}
.sortable:hover .sort-arrow {
opacity: 0.6;
}
.sortable.asc .sort-arrow {
opacity: 1;
color: #38bdf8;
transform: rotate(180deg);
}
.sortable.desc .sort-arrow {
opacity: 1;
color: #38bdf8;
transform: rotate(0deg);
}
/* Checkbox */
.dl-checkbox {
width: 15px;
height: 15px;
accent-color: #38bdf8;
cursor: pointer;
}
/* Member info */
.member-av {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--av, #6366f1);
color: #fff;
font-size: 0.7rem;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
opacity: 0.9;
}
.member-info {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.member-name {
font-size: 0.875rem;
font-weight: 500;
color: #e2e8f0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.member-email {
font-size: 0.72rem;
color: #64748b;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Role badges */
.role-badge {
font-size: 0.65rem;
font-weight: 600;
padding: 2px 9px;
border-radius: 20px;
white-space: nowrap;
}
.role-badge--admin {
background: rgba(167, 139, 250, 0.15);
color: #a78bfa;
}
.role-badge--editor {
background: rgba(56, 189, 248, 0.12);
color: #38bdf8;
}
.role-badge--viewer {
background: rgba(148, 163, 184, 0.1);
color: #94a3b8;
}
/* Status badges */
.status-badge {
font-size: 0.65rem;
font-weight: 600;
padding: 2px 9px;
border-radius: 20px;
white-space: nowrap;
display: flex;
align-items: center;
gap: 5px;
}
.status-badge::before {
content: "";
width: 5px;
height: 5px;
border-radius: 50%;
display: inline-block;
}
.status-badge--active {
background: rgba(52, 211, 153, 0.12);
color: #34d399;
}
.status-badge--active::before {
background: #34d399;
}
.status-badge--inactive {
background: rgba(148, 163, 184, 0.1);
color: #94a3b8;
}
.status-badge--inactive::before {
background: #94a3b8;
}
.status-badge--pending {
background: rgba(251, 191, 36, 0.12);
color: #fbbf24;
}
.status-badge--pending::before {
background: #fbbf24;
}
/* Joined date */
.dl-col--joined {
font-size: 0.8rem;
color: #64748b;
white-space: nowrap;
}
/* Action menu */
.action-menu-btn {
width: 28px;
height: 28px;
background: transparent;
border: none;
color: #64748b;
font-size: 1.125rem;
cursor: pointer;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.15s, color 0.15s;
line-height: 1;
}
.action-menu-btn:hover {
background: rgba(255, 255, 255, 0.08);
color: #e2e8f0;
}
.action-dropdown {
position: absolute;
top: calc(100% - 4px);
right: 8px;
background: #263044;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
overflow: hidden;
display: none;
flex-direction: column;
min-width: 110px;
z-index: 20;
}
.action-dropdown.open {
display: flex;
}
.action-item {
padding: 0.5rem 0.875rem;
background: transparent;
border: none;
color: #cbd5e1;
font-size: 0.8125rem;
text-align: left;
cursor: pointer;
transition: background 0.12s;
white-space: nowrap;
}
.action-item:hover {
background: rgba(255, 255, 255, 0.07);
}
.action-item--danger {
color: #f87171;
}
.action-item--danger:hover {
background: rgba(239, 68, 68, 0.1);
}
/* ── Empty state ── */
.dl-empty {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
padding: 3rem 1rem;
color: #475569;
font-size: 0.875rem;
}
/* ── Edit Modal ── */
.dl-modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
display: none;
align-items: center;
justify-content: center;
z-index: 100;
backdrop-filter: blur(2px);
}
.dl-modal-backdrop.open {
display: flex;
}
.dl-modal {
background: #1e293b;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 1rem;
width: min(420px, 90vw);
animation: modalIn 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes modalIn {
from {
opacity: 0;
transform: scale(0.95) translateY(-8px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.dl-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.125rem 1.25rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}
.dl-modal-title {
font-size: 0.9375rem;
font-weight: 700;
color: #f1f5f9;
}
.dl-modal-close {
background: transparent;
border: none;
color: #64748b;
cursor: pointer;
padding: 4px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.dl-modal-close:hover {
color: #f1f5f9;
}
.dl-modal-body {
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 0.875rem;
}
.dl-modal-footer {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
padding: 1rem 1.25rem;
border-top: 1px solid rgba(255, 255, 255, 0.07);
}
.edit-field-group {
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.edit-label {
font-size: 0.8125rem;
font-weight: 500;
color: #94a3b8;
}
.edit-input {
padding: 0.5625rem 0.75rem;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0.5rem;
color: #f1f5f9;
font-size: 0.875rem;
font-family: inherit;
outline: none;
transition: border-color 0.15s;
width: 100%;
}
.edit-input:focus {
border-color: #38bdf8;
}
.edit-input option {
background: #1e293b;
}
.edit-btn-cancel {
padding: 0.5625rem 1rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0.5rem;
color: #94a3b8;
font-size: 0.875rem;
cursor: pointer;
font-family: inherit;
}
.edit-btn-cancel:hover {
background: rgba(255, 255, 255, 0.1);
color: #f1f5f9;
}
.edit-btn-save {
padding: 0.5625rem 1.125rem;
background: #38bdf8;
border: none;
border-radius: 0.5rem;
color: #0f172a;
font-size: 0.875rem;
font-weight: 700;
cursor: pointer;
font-family: inherit;
}
.edit-btn-save:hover {
background: #7dd3fc;
}
/* ── Responsive ── */
@media (max-width: 720px) {
.dl-row {
grid-template-columns: 36px 1fr 100px 90px 52px;
}
.dl-col--joined {
display: none;
}
.dl-search input {
width: 160px;
}
}
@media (max-width: 520px) {
.dl-row {
grid-template-columns: 36px 1fr 90px 52px;
}
.dl-col--status {
display: none;
}
.dl-search {
display: none;
}
}(() => {
const dataList = document.getElementById("dataList");
const searchInput = document.getElementById("searchInput");
const selectAll = document.getElementById("selectAll");
const bulkBar = document.getElementById("bulkBar");
const bulkCount = document.getElementById("bulkCount");
const rowCount = document.getElementById("rowCount");
const emptyState = document.getElementById("emptyState");
let sortState = { col: null, dir: "asc" };
// ── Helpers ──
function getRows() {
return Array.from(dataList.querySelectorAll(".dl-row--data"));
}
function updateRowCount() {
const visible = getRows().filter((r) => r.style.display !== "none").length;
const total = getRows().length;
rowCount.textContent = `${visible}${visible !== total ? " of " + total : ""} member${total !== 1 ? "s" : ""}`;
emptyState.style.display = visible === 0 ? "flex" : "none";
}
function updateBulkBar() {
const checked = dataList.querySelectorAll(".row-check:checked");
const allVisible = getRows().filter((r) => r.style.display !== "none");
const allChecked = allVisible.every((r) => r.querySelector(".row-check").checked);
const someChecked = allVisible.some((r) => r.querySelector(".row-check").checked);
selectAll.checked = allChecked && allVisible.length > 0;
selectAll.indeterminate = someChecked && !allChecked;
if (checked.length > 0) {
bulkBar.classList.add("visible");
bulkCount.textContent = `${checked.length} selected`;
} else {
bulkBar.classList.remove("visible");
}
getRows().forEach((row) => {
const cb = row.querySelector(".row-check");
row.classList.toggle("selected", cb.checked);
});
}
// ── Search ──
searchInput.addEventListener("input", () => {
const q = searchInput.value.trim().toLowerCase();
getRows().forEach((row) => {
const name = (row.dataset.name || "").toLowerCase();
const email = (row.dataset.email || "").toLowerCase();
row.style.display = !q || name.includes(q) || email.includes(q) ? "" : "none";
});
updateRowCount();
updateBulkBar();
});
// ── Select All ──
selectAll.addEventListener("change", () => {
const visibleRows = getRows().filter((r) => r.style.display !== "none");
visibleRows.forEach((row) => {
row.querySelector(".row-check").checked = selectAll.checked;
});
updateBulkBar();
});
// ── Row checkboxes (delegated) ──
dataList.addEventListener("change", (e) => {
if (e.target.classList.contains("row-check")) updateBulkBar();
});
// ── Sort ──
document.querySelectorAll(".sortable").forEach((header) => {
header.addEventListener("click", () => {
const col = header.dataset.sort;
if (sortState.col === col) {
sortState.dir = sortState.dir === "asc" ? "desc" : "asc";
} else {
sortState.col = col;
sortState.dir = "asc";
}
// Update header indicators
document.querySelectorAll(".sortable").forEach((h) => {
h.classList.remove("asc", "desc");
});
header.classList.add(sortState.dir);
// Sort rows
const rows = getRows();
rows.sort((a, b) => {
let valA = (a.dataset[col] || "").toLowerCase();
let valB = (b.dataset[col] || "").toLowerCase();
// Date comparison
if (col === "joined") {
valA = new Date(a.dataset.joined);
valB = new Date(b.dataset.joined);
return sortState.dir === "asc" ? valA - valB : valB - valA;
}
return sortState.dir === "asc" ? valA.localeCompare(valB) : valB.localeCompare(valA);
});
rows.forEach((row) => dataList.appendChild(row));
});
});
// ── Action dropdown ──
let openDropdown = null;
dataList.addEventListener("click", (e) => {
// Toggle dropdown
const menuBtn = e.target.closest(".action-menu-btn");
if (menuBtn) {
e.stopPropagation();
const dropdown = menuBtn.nextElementSibling;
if (openDropdown && openDropdown !== dropdown) {
openDropdown.classList.remove("open");
}
dropdown.classList.toggle("open");
openDropdown = dropdown.classList.contains("open") ? dropdown : null;
return;
}
// Action item
const actionItem = e.target.closest(".action-item");
if (actionItem) {
const row = actionItem.closest(".dl-row--data");
if (actionItem.classList.contains("action-item--danger") && row) {
row.classList.add("removing");
setTimeout(() => {
row.remove();
updateRowCount();
updateBulkBar();
}, 300);
}
if (openDropdown) {
openDropdown.classList.remove("open");
openDropdown = null;
}
}
});
// Close dropdown on outside click
document.addEventListener("click", (e) => {
if (openDropdown && !e.target.closest(".dl-col--actions")) {
openDropdown.classList.remove("open");
openDropdown = null;
}
});
// ── Edit Modal ──
const editModalBackdrop = document.getElementById("editModalBackdrop");
let editingRow = null;
function openEditModal(row) {
editingRow = row;
document.getElementById("editName").value = row.dataset.name || "";
document.getElementById("editEmail").value = row.dataset.email || "";
document.getElementById("editRole").value = row.dataset.role || "Viewer";
document.getElementById("editStatus").value = row.dataset.status || "Active";
editModalBackdrop.classList.add("open");
}
function closeEditModal() {
editModalBackdrop.classList.remove("open");
editingRow = null;
}
document.getElementById("editModalClose").addEventListener("click", closeEditModal);
document.getElementById("editModalCancel").addEventListener("click", closeEditModal);
editModalBackdrop.addEventListener("click", (e) => {
if (e.target === editModalBackdrop) closeEditModal();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") closeEditModal();
});
document.getElementById("editModalSave").addEventListener("click", () => {
if (!editingRow) return;
const newName = document.getElementById("editName").value.trim();
const newEmail = document.getElementById("editEmail").value.trim();
const newRole = document.getElementById("editRole").value;
const newStatus = document.getElementById("editStatus").value;
if (!newName || !newEmail) return;
// Update data attributes
editingRow.dataset.name = newName;
editingRow.dataset.email = newEmail;
editingRow.dataset.role = newRole;
editingRow.dataset.status = newStatus;
// Update visible cells
const nameEl = editingRow.querySelector(".member-name");
const emailEl = editingRow.querySelector(".member-email");
const roleEl = editingRow.querySelector(".role-badge");
const statusEl = editingRow.querySelector(".status-badge");
if (nameEl) nameEl.textContent = newName;
if (emailEl) emailEl.textContent = newEmail;
if (roleEl) {
roleEl.textContent = newRole;
roleEl.className = `role-badge role-badge--${newRole.toLowerCase()}`;
}
if (statusEl) {
statusEl.textContent = newStatus;
statusEl.className = `status-badge status-badge--${newStatus.toLowerCase()}`;
}
closeEditModal();
});
// Wire Edit buttons via delegated click on dataList
dataList.addEventListener("click", (e) => {
const actionItem = e.target.closest(".action-item");
if (
actionItem &&
!actionItem.classList.contains("action-item--danger") &&
actionItem.textContent.trim() === "Edit"
) {
const row = actionItem.closest(".dl-row--data");
if (row) openEditModal(row);
if (openDropdown) {
openDropdown.classList.remove("open");
openDropdown = null;
}
}
});
// ── Init ──
updateRowCount();
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Data List</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="dl-container">
<!-- Header -->
<div class="dl-header">
<div class="dl-header-left">
<h2 class="dl-title">Team Members</h2>
<span class="dl-count" id="rowCount">8 members</span>
</div>
<div class="dl-header-right">
<div class="dl-search">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#64748b" stroke-width="2">
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
<input type="text" id="searchInput" placeholder="Search by name or email…" />
</div>
<button class="btn-add">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
</svg>
Add Member
</button>
</div>
</div>
<!-- Bulk action bar -->
<div class="bulk-bar" id="bulkBar">
<span class="bulk-count" id="bulkCount">0 selected</span>
<div class="bulk-actions">
<button class="bulk-btn bulk-btn--warn">Deactivate</button>
<button class="bulk-btn bulk-btn--danger">Delete</button>
<button class="bulk-btn">Export</button>
</div>
</div>
<!-- List -->
<div class="dl-list-wrapper">
<!-- Column headers -->
<div class="dl-row dl-row--header">
<div class="dl-col dl-col--check">
<input type="checkbox" id="selectAll" class="dl-checkbox" />
</div>
<div class="dl-col dl-col--name sortable" data-sort="name">
<span>Name</span>
<svg class="sort-arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="m18 15-6-6-6 6"/></svg>
</div>
<div class="dl-col dl-col--role sortable" data-sort="role">
<span>Role</span>
<svg class="sort-arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="m18 15-6-6-6 6"/></svg>
</div>
<div class="dl-col dl-col--status sortable" data-sort="status">
<span>Status</span>
<svg class="sort-arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="m18 15-6-6-6 6"/></svg>
</div>
<div class="dl-col dl-col--joined sortable" data-sort="joined">
<span>Joined</span>
<svg class="sort-arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="m18 15-6-6-6 6"/></svg>
</div>
<div class="dl-col dl-col--actions"><span>Actions</span></div>
</div>
<!-- Data rows -->
<div id="dataList">
<div class="dl-row dl-row--data" data-name="Alex Chen" data-email="alex.chen@company.io" data-role="Admin" data-status="Active" data-joined="2023-01-15">
<div class="dl-col dl-col--check"><input type="checkbox" class="dl-checkbox row-check" /></div>
<div class="dl-col dl-col--name">
<div class="member-av" style="--av:#6366f1">AC</div>
<div class="member-info"><span class="member-name">Alex Chen</span><span class="member-email">alex.chen@company.io</span></div>
</div>
<div class="dl-col dl-col--role"><span class="role-badge role-badge--admin">Admin</span></div>
<div class="dl-col dl-col--status"><span class="status-badge status-badge--active">Active</span></div>
<div class="dl-col dl-col--joined">Jan 15, 2023</div>
<div class="dl-col dl-col--actions"><button class="action-menu-btn" aria-label="Actions">⋮</button><div class="action-dropdown"><button class="action-item">Edit</button><button class="action-item action-item--danger">Delete</button></div></div>
</div>
<div class="dl-row dl-row--data" data-name="Sarah Kim" data-email="sarah.kim@acme.io" data-role="Editor" data-status="Active" data-joined="2023-03-22">
<div class="dl-col dl-col--check"><input type="checkbox" class="dl-checkbox row-check" /></div>
<div class="dl-col dl-col--name">
<div class="member-av" style="--av:#0ea5e9">SK</div>
<div class="member-info"><span class="member-name">Sarah Kim</span><span class="member-email">sarah.kim@acme.io</span></div>
</div>
<div class="dl-col dl-col--role"><span class="role-badge role-badge--editor">Editor</span></div>
<div class="dl-col dl-col--status"><span class="status-badge status-badge--active">Active</span></div>
<div class="dl-col dl-col--joined">Mar 22, 2023</div>
<div class="dl-col dl-col--actions"><button class="action-menu-btn" aria-label="Actions">⋮</button><div class="action-dropdown"><button class="action-item">Edit</button><button class="action-item action-item--danger">Delete</button></div></div>
</div>
<div class="dl-row dl-row--data" data-name="Marcus Reed" data-email="m.reed@corp.com" data-role="Viewer" data-status="Inactive" data-joined="2023-05-10">
<div class="dl-col dl-col--check"><input type="checkbox" class="dl-checkbox row-check" /></div>
<div class="dl-col dl-col--name">
<div class="member-av" style="--av:#64748b">MR</div>
<div class="member-info"><span class="member-name">Marcus Reed</span><span class="member-email">m.reed@corp.com</span></div>
</div>
<div class="dl-col dl-col--role"><span class="role-badge role-badge--viewer">Viewer</span></div>
<div class="dl-col dl-col--status"><span class="status-badge status-badge--inactive">Inactive</span></div>
<div class="dl-col dl-col--joined">May 10, 2023</div>
<div class="dl-col dl-col--actions"><button class="action-menu-btn" aria-label="Actions">⋮</button><div class="action-dropdown"><button class="action-item">Edit</button><button class="action-item action-item--danger">Delete</button></div></div>
</div>
<div class="dl-row dl-row--data" data-name="Julia Lee" data-email="julia.lee@startup.dev" data-role="Editor" data-status="Pending" data-joined="2024-01-08">
<div class="dl-col dl-col--check"><input type="checkbox" class="dl-checkbox row-check" /></div>
<div class="dl-col dl-col--name">
<div class="member-av" style="--av:#f59e0b">JL</div>
<div class="member-info"><span class="member-name">Julia Lee</span><span class="member-email">julia.lee@startup.dev</span></div>
</div>
<div class="dl-col dl-col--role"><span class="role-badge role-badge--editor">Editor</span></div>
<div class="dl-col dl-col--status"><span class="status-badge status-badge--pending">Pending</span></div>
<div class="dl-col dl-col--joined">Jan 8, 2024</div>
<div class="dl-col dl-col--actions"><button class="action-menu-btn" aria-label="Actions">⋮</button><div class="action-dropdown"><button class="action-item">Edit</button><button class="action-item action-item--danger">Delete</button></div></div>
</div>
<div class="dl-row dl-row--data" data-name="Tom Nakamura" data-email="t.nakamura@labs.ai" data-role="Admin" data-status="Active" data-joined="2022-11-30">
<div class="dl-col dl-col--check"><input type="checkbox" class="dl-checkbox row-check" /></div>
<div class="dl-col dl-col--name">
<div class="member-av" style="--av:#10b981">TN</div>
<div class="member-info"><span class="member-name">Tom Nakamura</span><span class="member-email">t.nakamura@labs.ai</span></div>
</div>
<div class="dl-col dl-col--role"><span class="role-badge role-badge--admin">Admin</span></div>
<div class="dl-col dl-col--status"><span class="status-badge status-badge--active">Active</span></div>
<div class="dl-col dl-col--joined">Nov 30, 2022</div>
<div class="dl-col dl-col--actions"><button class="action-menu-btn" aria-label="Actions">⋮</button><div class="action-dropdown"><button class="action-item">Edit</button><button class="action-item action-item--danger">Delete</button></div></div>
</div>
<div class="dl-row dl-row--data" data-name="Priya Patel" data-email="p.patel@design.co" data-role="Viewer" data-status="Active" data-joined="2024-02-14">
<div class="dl-col dl-col--check"><input type="checkbox" class="dl-checkbox row-check" /></div>
<div class="dl-col dl-col--name">
<div class="member-av" style="--av:#ec4899">PP</div>
<div class="member-info"><span class="member-name">Priya Patel</span><span class="member-email">p.patel@design.co</span></div>
</div>
<div class="dl-col dl-col--role"><span class="role-badge role-badge--viewer">Viewer</span></div>
<div class="dl-col dl-col--status"><span class="status-badge status-badge--active">Active</span></div>
<div class="dl-col dl-col--joined">Feb 14, 2024</div>
<div class="dl-col dl-col--actions"><button class="action-menu-btn" aria-label="Actions">⋮</button><div class="action-dropdown"><button class="action-item">Edit</button><button class="action-item action-item--danger">Delete</button></div></div>
</div>
<div class="dl-row dl-row--data" data-name="David Okonkwo" data-email="d.okonkwo@fintech.ng" data-role="Editor" data-status="Inactive" data-joined="2023-08-19">
<div class="dl-col dl-col--check"><input type="checkbox" class="dl-checkbox row-check" /></div>
<div class="dl-col dl-col--name">
<div class="member-av" style="--av:#8b5cf6">DO</div>
<div class="member-info"><span class="member-name">David Okonkwo</span><span class="member-email">d.okonkwo@fintech.ng</span></div>
</div>
<div class="dl-col dl-col--role"><span class="role-badge role-badge--editor">Editor</span></div>
<div class="dl-col dl-col--status"><span class="status-badge status-badge--inactive">Inactive</span></div>
<div class="dl-col dl-col--joined">Aug 19, 2023</div>
<div class="dl-col dl-col--actions"><button class="action-menu-btn" aria-label="Actions">⋮</button><div class="action-dropdown"><button class="action-item">Edit</button><button class="action-item action-item--danger">Delete</button></div></div>
</div>
<div class="dl-row dl-row--data" data-name="Emma Walsh" data-email="emma.walsh@cloud.io" data-role="Viewer" data-status="Pending" data-joined="2024-03-01">
<div class="dl-col dl-col--check"><input type="checkbox" class="dl-checkbox row-check" /></div>
<div class="dl-col dl-col--name">
<div class="member-av" style="--av:#06b6d4">EW</div>
<div class="member-info"><span class="member-name">Emma Walsh</span><span class="member-email">emma.walsh@cloud.io</span></div>
</div>
<div class="dl-col dl-col--role"><span class="role-badge role-badge--viewer">Viewer</span></div>
<div class="dl-col dl-col--status"><span class="status-badge status-badge--pending">Pending</span></div>
<div class="dl-col dl-col--joined">Mar 1, 2024</div>
<div class="dl-col dl-col--actions"><button class="action-menu-btn" aria-label="Actions">⋮</button><div class="action-dropdown"><button class="action-item">Edit</button><button class="action-item action-item--danger">Delete</button></div></div>
</div>
</div>
<!-- Empty state -->
<div class="dl-empty" id="emptyState" style="display:none">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#475569" stroke-width="1.5">
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
<span>No members match your search</span>
</div>
</div>
</div>
<!-- Edit modal -->
<div class="dl-modal-backdrop" id="editModalBackdrop">
<div class="dl-modal" role="dialog">
<div class="dl-modal-header">
<h3 class="dl-modal-title">Edit Member</h3>
<button class="dl-modal-close" id="editModalClose">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 6 6 18M6 6l12 12"/></svg>
</button>
</div>
<div class="dl-modal-body">
<div class="edit-field-group">
<label class="edit-label">Name</label>
<input type="text" class="edit-input" id="editName" />
</div>
<div class="edit-field-group">
<label class="edit-label">Email</label>
<input type="email" class="edit-input" id="editEmail" />
</div>
<div class="edit-field-group">
<label class="edit-label">Role</label>
<select class="edit-input" id="editRole">
<option value="Admin">Admin</option>
<option value="Editor">Editor</option>
<option value="Viewer">Viewer</option>
</select>
</div>
<div class="edit-field-group">
<label class="edit-label">Status</label>
<select class="edit-input" id="editStatus">
<option value="Active">Active</option>
<option value="Inactive">Inactive</option>
<option value="Pending">Pending</option>
</select>
</div>
</div>
<div class="dl-modal-footer">
<button class="edit-btn-cancel" id="editModalCancel">Cancel</button>
<button class="edit-btn-save" id="editModalSave">Save Changes</button>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>Data List
A production-ready data list for team and user management. Supports real-time search, multi-column sorting with direction indicators, bulk checkbox selection with a contextual action bar, and inline action dropdowns per row. Zero dependencies — pure HTML, CSS, and vanilla JS.
Features
- Live search filtering by name or email
- Click-to-sort on any column with ascending/descending toggle and visual arrow indicator
- Select-all checkbox with indeterminate state; bulk action bar appears on selection
- Status badges (Active/Inactive/Pending) and role badges (Admin/Editor/Viewer) with distinct colors
- Per-row action dropdown (Edit/Delete) with smooth animation; delete removes the row