A small HTML table does not need a search library. Read the search field, compare it with the loaded rows and hide rows that do not match. The important boundary is loaded rows: this technique does not query a database or search records on another page.
The example below searches product names and categories. It keeps the header visible and reports the number of matching rows, including zero matches. Using the input event means typing, pasting and clearing the search field all use the same filtering function.
A complete table-filter example
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Search a product table</title>
<style>
body { font: 1rem/1.6 system-ui; margin: 2rem; }
input { display: block; padding: .6rem; margin-block: .5rem 1rem; }
table { border-collapse: collapse; }
th, td { border: 1px solid #687786; padding: .5rem 1rem; text-align: left; }
[hidden] { display: none !important; }
</style>
<h1>Product list</h1>
<label for="search">Search by name or category</label>
<input id="search" type="search" aria-controls="products" autocomplete="off">
<p id="count" role="status" aria-live="polite" aria-atomic="true"></p>
<table id="products">
<caption>Example inventory</caption>
<thead><tr><th scope="col">Product</th><th scope="col">Category</th></tr></thead>
<tbody>
<tr><td>USB keyboard</td><td>Accessories</td></tr>
<tr><td>Ethernet cable</td><td>Networking</td></tr>
<tr><td>Wireless mouse</td><td>Accessories</td></tr>
<tr><td>Network switch</td><td>Networking</td></tr>
</tbody>
</table>
<script>
const search = document.querySelector('#search');
const rows = [...document.querySelectorAll('#products tbody tr')];
const count = document.querySelector('#count');
const normalize = text => text.trim().toLocaleLowerCase('en');
const entries = rows.map(row => ({
row,
text: normalize([...row.cells].map(cell => cell.textContent).join(' '))
}));
function filterRows() {
const query = normalize(search.value);
let visible = 0;
for (const entry of entries) {
const match = entry.text.includes(query);
entry.row.hidden = !match;
if (match) visible++;
}
count.textContent = visible + ' of ' + rows.length + ' products shown';
}
search.addEventListener('input', filterRows);
filterRows();
</script>
</html>
Why search cell text rather than HTML?
The filter compares text values, not serialized tags. A query containing angle brackets is still just a query; it is never inserted into the document as markup. The result counter is also assigned through textContent. See MDN's textContent reference for the distinction from reading or writing HTML.
Only tbody rows are selected, so the column labels do not disappear. Joining cell values with spaces prevents the last word in one cell and the first word in the next from becoming one search term. An empty query matches all entries, which makes clearing the field behave like a reset.
Decide what a match should mean
This example uses a case-insensitive substring match with English casing rules. Searching for net finds both the Ethernet cable and network switch. Searching for wireless accessories does not match the mouse row because those words are not adjacent in its combined text. This is not a fuzzy or multi-keyword search engine.
Choose the locale deliberately for another language; Turkish casing, for example, distinguishes dotted and dotless I. Accent folding, punctuation removal and word-by-word matching are separate product decisions, not automatic consequences of lowercasing.
Know when to move the search to the server
The searchable strings are cached when the script starts. If your application edits or appends rows later, rebuild the cache or update the affected entry. For a paginated catalogue, search the underlying dataset on the server and return a limited result set. Hiding rows in the browser neither protects confidential data nor removes it from the page source.
Before shipping, paste a query, clear it, try an unmatched term and confirm that the header and result count remain useful. Test with the real table size: a four-row example is not evidence that thousands of complex rows will filter smoothly.
Related Articles