Data Table — Free HTML CSS JS Snippet

Data Table · Tables · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Live search via row.textContent.toLowerCase().includes() — searches all columns in one check
Select-all checkbox: toggleAll() syncs all .row-check inputs to header state
Status badges: .badge.green/.yellow/.red with pulsing animated dot via ::before
Avatar cells: inline-gradient circle with initials — no image load, works offline
Edit and Remove action buttons per row with hover accent transition
Pagination controls with active page indicator
tr: hover row highlight via background-color transition
border-collapse table with sticky-style header background
Export as HTML file, React JSX component, or React + Tailwind CSS
Live split-pane editor — preview updates as you type

About this UI Snippet

Data Table — Search Filter, Status Badges, Row Actions & Select-All Checkbox

Screenshot of the Data Table snippet rendered live

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:

text
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

  1. 1
    Search 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.
  2. 2
    Update 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).
  3. 3
    Add 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.
  4. 4
    Change 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.
  5. 5
    Wire 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(); }.
  6. 6
    Export 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

User and team management admin tables
Display team members, customers, or subscribers with avatar initials, status badges, role labels, and per-row edit/remove actions. Wire to a users API endpoint to render real data.
Order history and transaction tables
Show order rows with status badges (Pending/Shipped/Delivered/Cancelled) and action buttons for opening order detail modals or initiating refunds. The live search lets support staff find orders instantly.
Admin panel foundation for any data type
The foundation of any admin panel. Extend with click-to-sort from the Sortable Table snippet, bulk action buttons activated by the select-all checkbox, and a CSV export via Blob URL.
Learn textContent-based client-side table search
filterTable() searches all columns with one querySelectorAll + forEach. row.textContent returns every visible character — no per-column selector, no column index logic. Study the function to understand why this approach scales to any column count.
Combine with Sortable Table for full data grid
The Sortable Table snippet in this library uses the same table structure. Add the sortBy() function from that snippet to this table to get search + sort in one component without conflict.
Employee and HR management dashboards
Display employee records with department, role, status, and join date. Add a department dropdown filter alongside the text search to narrow rows by team — both filters can run simultaneously on the same row set.

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.