Plain JavaScript · No dependencies
Drop in two files, pass in your data, and you're done.
A fully interactive table - sort, search, paginate, toggle columns, export CSV.
It's the 20 minutes you spend before you write a single line of code:
DataTables, Simple-DataTables, Grid.js? Compare docs, stars, bundle size…
Learn how this specific library wants your data formatted
Override styles, disable features you don't need, dig through GitHub issues
Need one small custom behavior - not supported, or locked behind a paid tier
"It's just a table.
I don't want to spend an afternoon on this."
| DataTables | Simple-DataTables | Grid.js | Json Table Mate | |
|---|---|---|---|---|
| Bundle size (minified) |
~180–210KB total (includes jQuery) |
~70–90KB | ~50–70KB | ~13KB |
| Bundle size (gzipped) |
~50–70KB total (includes jQuery) |
~20–28KB | ~12–22KB | ~5KB |
| Columns auto-detected from data | ✓ | ✓ | ✓ | ✓ |
| Simple initialization | ✓ | ✓ | ✓ | ✓ |
| Modern / readable codebase | ✗ | ✓ | ✓ | ✓ |
Sizes are approximate and vary by build and configuration. Gzipped size reflects typical network transfer size. DataTables requires jQuery, which increases total payload.
Click any header to sort. Handles strings, numbers, and nulls correctly.
Filters rows as you type. Searches across all columns simultaneously.
Configurable page size. Handles hundreds of rows cleanly.
Users can show/hide individual columns. Useful for wide datasets.
Downloads the current filtered, sorted view.
Pass a render function
per column. Return any HTML you like.
Horizontally scrollable on small screens.
Matching text is highlighted as you type.
Pass an onRowClick handler and get the full row object on click.
Override 7 CSS variables to restyle everything - no library changes needed.
Two files. Drop them in and go. Every option has a default. Nothing is required.
JsonTableMate.render() directlyIf your data comes from a fetch or a JS variable, skip the textarea and button entirely:
| Option | Type | Default | Description |
|---|---|---|---|
| Sorting & Search | |||
isSortable |
boolean | true | Click column headers to sort ascending/descending |
defaultSortBy |
string | null | Column name to sort by on load; table still renders if the column doesn't exist (logs a console warning) |
defaultSortDirection |
string | 'asc' | Initial sort direction - 'asc' or 'desc' |
isSearchable |
boolean | true | Show a search box that filters all columns as you type |
| Pagination | |||
isPaginated |
boolean | true | Set to false to show all rows at once with no pagination controls |
defaultPageSize |
number | 10 | Number of rows per page on initial load |
paginationStyle |
string | 'full' | 'full' shows numbered page buttons with ellipsis; 'simple' shows Prev / Next only |
hasPageSizeSelector |
boolean | true | Show a dropdown letting users change the number of rows per page |
pageSizeOptions |
number[] | [10, 25, 50, 100] | Choices in the page-size dropdown (requires hasPageSizeSelector) |
| Toolbar | |||
hasColumnToggle |
boolean | true | Show a dropdown letting users hide/show individual columns |
isExportable |
boolean | true | Show an Export CSV button; downloads the current filtered, sorted view |
exportFilename |
string | 'export.csv' | Filename for the downloaded CSV file |
| Interaction and Formatting | |||
onRowClick |
function | null | Called with the full row object when a row is clicked; adds a pointer cursor to rows |
tableClass |
string | 'jtm-table' | CSS class(es) on the <table> element - swap in Bootstrap or any other framework's classes |
columns |
object | {} | Per-column config: label, hidden, render - see below |
Use the columns object to set a display label, hide a column by default, or override how cells are rendered. All three keys are optional and can be combined.
render function injects its return value as innerHTML.
If your data comes from user input or an untrusted source, escape values inside your render function before returning them.
Cells without a custom render are always safe - the library escapes them automatically.
| Method / Property | Description |
|---|---|
JsonTableMate.renderTable(inputEl, containerEl, options, errorEl) |
Reads JSON from a <textarea>, validates it, and renders the table. options and errorEl are optional. |
JsonTableMate.render(containerEl, dataArray, options) |
Renders directly from a JS array - no textarea needed. Use this when your data comes from a fetch or a variable. |
JsonTableMate.destroy(containerEl) |
Tears down the table, removes event listeners, and clears the container. Use before removing the element from the DOM (ex. in a SPA). |
Override any of these on :root (or a parent element) to restyle the table without touching the library CSS.
| Variable | Default | Controls |
|---|---|---|
--jtm-accent | #0d6efd | Active page button, sort icon, focus rings |
--jtm-border | #dee2e6 | Table border, toolbar button borders, select borders |
--jtm-header-bg | #f8f9fa | Column header background, button hover background |
--jtm-stripe-bg | #f8f9fa | Even-row stripe background |
--jtm-hover-bg | #e8f0fe | Row hover background |
--jtm-highlight-bg | #fff3cd | Search match highlight background |
--jtm-muted | #6c757d | Info text, empty-state text, ellipsis color |
You get the full, readable source. Don't like something? Open the file and change it. No build step, no abstraction layers, no pull requests.
That's what $9 buys you: code you understand and control, permanently.
One download. Everything you need.
Not sure? Try the live demo first.
You could copy the minified file from this demo. I won't stop you. But you'd get 13KB of unreadable soup — no comments, no structure, no way to customize anything. Reverse-engineering it will cost you more than an afternoon. The full, readable source is only $9.