More Tables Snippets
Data Table — Free HTML CSS JS Snippet
Data Table · Tables · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Data Table — Search Filter, Status Badges, Row Actions & Select-All Checkbox

If you are building an admin panel, a dashboard, or any feature that displays lists of records, a styled data table is one of the first components you need. This snippet gives you a complete, production-ready HTML CSS JavaScript data table with live search, status badges, row checkboxes with select-all, gradient avatar initials, Edit and Remove row actions, and pagination controls.
How the live search filter works
The search input calls filterTable(q) on every keyup event. filterTable iterates all tbody tr rows and reads row.textContent — which returns every visible text character in the entire row as a single string. A single row.textContent.toLowerCase().includes(term) check matches against name, email, role, department, and status simultaneously. Matching rows stay visible; non-matching rows get a .hidden class which applies display: none. This approach requires no per-column logic and handles any number of columns automatically.
Status badge design
Three badge variants show user or record status: .badge.green (Active), .badge.yellow (Away), and .badge.red (Offline). Each uses a rgba() tinted background matching the text colour, a 4px border radius, and a ::before pseudo-element dot with an inline animation. The dot pulses for Active users and stays static for others. This pattern works for any status set — Pending/Approved/Rejected, Open/In Progress/Closed, Online/Busy/Offline.
Select-all checkbox
The header checkbox calls toggleAll(cb), which reads cb.checked and sets every .row-check checkbox to the same state. This enables bulk operations — delete selected, export selected, tag selected — without a library. Add a data attribute to each row to store the record ID for easy batch processing.
Avatar initials with gradient
Each user row has a .av circle with gradient background and two-letter initials. Each gradient uses a distinct hue pair set via inline style on each .av element. No image loading, no broken img tags, no external CDN — the initials render immediately in all conditions including offline.
Row actions
Each row has Edit and Remove buttons in an .actions cell. Both use the same icon button style. Wiring these to real functionality takes two lines per button: the Edit button calls openModal(rowId), the Remove button calls row.closest("tr").remove() and re-runs the filter. Because action cells are the last column, they are always reachable even on a wide table.
Customising the table
Replace the sample HTML rows with your own data. Add columns by adding th headers and td cells — the table layout adapts automatically. Change badge classes to match your status set. Combine with the Sortable Table snippet to add click-to-sort on column headers, or add inline-edit cells and pagination controls for a full data grid.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to take the row.textContent trick on faith. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why reading a whole row's textContent and doing one lowercase includes check is enough to search every visible column at once, and what it would miss compared to searching structured field values directly. The same assistant can help you optimize it — ask whether toggling a hidden class on non-matching rows scales fine for a few dozen rows versus a few thousand, and at what row count you'd want to virtualize the table instead. It's also useful for extending the table: ask it to add multi-column sorting on top of the existing search, a bulk-delete action wired to the select-all checkbox, or server-side pagination that replaces the current all-rows-in-the-DOM approach. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a searchable data table with row selection in plain HTML, CSS, and JavaScript — no library.
Requirements:
- A table rendering rows of user records, each with an avatar circle showing two-letter initials on a gradient background (not an image), a name and email stacked in one cell, a role, a department, a colored status badge, a join date, and Edit/Remove action buttons.
- A search input that filters visible rows on every keystroke by checking a single row's full rendered text content (not per-column comparisons) against the lowercased search term, so the same one check matches name, email, role, department, or status without any column-specific logic.
- Non-matching rows must be hidden via a CSS class that sets display: none rather than being removed from the DOM, so filtering is reversible without re-rendering the table.
- Update a visible "Showing X of Y" counter every time the filter runs, based on how many rows currently match.
- A header checkbox that, when toggled, sets every row's individual checkbox to match its own checked state in one operation.
- Style at least three distinct status badge variants (e.g. active, away, offline) each with a tinted background, a matching text color, and a small colored dot indicator.
- A pagination control row at the bottom with previous/next buttons and numbered page buttons, with the current page visually distinguished and the previous button disabled on the first page.Step by step
How to Use
- 1Search to filter rowsType in the search box to instantly filter rows by any visible text — name, email, role, department, or status. The filter works across all columns simultaneously without page reload.
- 2Update table dataIn the HTML panel, replace each tbody tr with your own data rows. Update the avatar initials, gradient colours in the inline style, names, roles, and badge class (green/yellow/red).
- 3Add or remove columnsAdd a new th in the header and a matching td in every row. The table auto-adjusts column widths. Remove columns by deleting the th and all corresponding td elements.
- 4Change status badge typesApply .badge.green for active/online states, .badge.yellow for pending/away states, and .badge.red for offline/rejected states. Edit the badge text directly in the HTML span.
- 5Wire row action buttonsAdd onclick handlers to the Edit and Remove buttons in each row. Edit: btn.onclick = () => openModal(rowId). Remove: btn.onclick = () => { btn.closest("tr").remove(); }.
- 6Export in your formatClick "HTML" for a standalone file, "JSX" for a React component where rows come from a useState array, or "Tailwind" for a React + Tailwind CSS version.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
filterTable(q) reads row.textContent on each tbody tr. textContent returns every visible text character in the entire row as one concatenated string — name, email, role, department, status, and date all in one. A single .toLowerCase().includes(term) check matches any of them. Rows that do not match get class="hidden" which applies display: none via CSS. Rows that do match have .hidden removed.
Create a new tr with innerHTML: const row = document.createElement("tr"); row.innerHTML = "<td>...</td>"; document.querySelector("tbody").appendChild(row). If you are using the search filter, call filterTable(document.getElementById("search").value) after appending so the new row is immediately filtered by the current search term.
Both snippets use the same thead/tbody table structure. Copy the sortBy() function and the data-col/data-type attributes from the Sortable Table snippet into this file. Add onclick="sortBy(this)" to each th. Both filterTable() and sortBy() operate on the tbody rows independently and do not conflict.
Collect visible rows: const rows = [...document.querySelectorAll("tbody tr:not(.hidden)")]. Map each to a CSV line: rows.map(r => [...r.querySelectorAll("td")].slice(1, -1).map(td => td.textContent.trim()).join(",")).join(" "). Create a download link: const a = document.createElement("a"); a.href = URL.createObjectURL(new Blob([csv], {type: "text/csv"})); a.download = "data.csv"; a.click().
After the user checks rows, read the selection: const selected = [...document.querySelectorAll(".row-check:checked")].map(cb => cb.closest("tr")). Store a record ID in each row as data-id="123". Read it with tr.dataset.id. Then POST the IDs to your API for bulk delete, bulk tag, or bulk export. Reset checkboxes after the action completes.
Yes. Click "JSX" to download a React component. Manage rows as an array in useState. Apply filtering via rows.filter(r => Object.values(r).join(" ").toLowerCase().includes(query)). Render each row with a DataRow component that receives edit and delete callback props. For Next.js, fetch rows server-side with getServerSideProps and pass as initial props.