Vanilla JS Grid Filtering: Column Filters, Quick Filters, and Custom Predicates

Vanilla TSTutorialFiltering

Column filters, quick search, and custom predicates for vanilla TypeScript data grids—strict TypeScript examples for simple-table-core and a comparison to Tabulator, Grid.js, and Handsontable.

For Vanilla JS / TypeScript developers building data grids in 2026.

Filtering is the bread-and-butter feature users hit before sorting or even rendering all rows. Get the typing wrong (string vs number vs date) and your filter UX feels broken.

This tutorial walks through column filters, a global quick filter, and custom predicates for the vanilla JS / TS data grid landscape and shows the simple-table-core setup with strict TypeScript.

If you want a framework-agnostic, MIT-licensed grid with built-in filtering, simple-table-core is ~70 kB gzipped and works in any host.

Why it matters

Faster discovery

Users find rows by typing or selecting; they don't scan thousands of rows visually.

Type-aware filtering

Strings need contains/equals; numbers need >, <, between; dates need calendar pickers.

Combinable with sort/group

Filter, then sort, then group. The order matters for performance and UX.

Server vs client

Small datasets filter client-side; large ones round-trip to the server. Both are common.

Vanilla JS / TypeScript library comparison

LibrarySupportNotes
simple-table-coreBuilt-in column filters + quick filterfilterable: true + type-aware predicates; quickFilter option for global search.
TabulatorBuilt-inheaderFilter on column defs; advanced filters supported.
Grid.jsPluginSearch plugin for global search; column filters require third-party.
HandsontableBuilt-in (commercial)Filters built-in but commercial license required.
jSpreadsheetBuilt-inSpreadsheet-style filters; less suited for typed data grids.

Implementation: simple-table-core

Set filterable: true globally or per-column for built-in filter chips. Add a quick-search input and bind it via the API. Plug in custom predicates for advanced cases.

For 100k+ rows, debounce the quick-filter input by 150-250ms. Use requestAnimationFrame or a small debounce helper—no framework required.

Common pitfalls

String filtering on numbers

Problem: Sorting and filtering treat "10" < "2" because they're strings.

Solution: Set the column type='number' so type-aware predicates kick in.

Memory leaks on SPA navigation

Problem: Filter listeners persist after the host element is removed.

Solution: Call table.dispose() on cleanup so listeners are removed.

Slow on every keystroke

Problem: Filtering 50k rows on every keystroke janks.

Solution: Debounce input by 150-250ms or filter server-side.

Date strings aren't filterable

Problem: ISO strings sort/filter alphabetically, which breaks for dates.

Solution: Set type='date' (or pass Date objects) so the grid knows to compare temporally.

Frequently asked questions

Can I filter server-side?
Yes. Listen to your input, fetch filtered rows from the API, and call table.setRows(newRows).
Does it work without a build step?
Yes. Import simple-table-core via esm.sh in a <script type="module"> tag.
Does filtering combine with virtualization?
Yes. The filtered row set is what the virtualizer renders, so 1M-row datasets remain smooth after filtering.

Wrap-up

Filtering in vanilla JS / TS is a single option on simple-table-core. Tabulator covers it natively too; Grid.js requires plugins; Handsontable is commercial.

Always set the right column type so type-aware predicates work, debounce on large datasets, and consider server-side filtering for 100k+ rows.

Add filtering to your vanilla TS grid

simple-table-core ships column filters, quick filter, and custom predicates in one MIT package—~70 kB gzipped, strict TypeScript, ESM-first.