A tiny, dependency-free, HTMX-friendly table enhancer: client-side sort,
search, and pagination for any plain HTML <table>. Type-aware (numbers,
dates, text) via data-* attributes, so your display can stay pretty ($1,234.50,
Jan 25, 2026) while sorting/searching use the underlying values. It also
re-initializes tables after HTMX swaps, so search/sort/pagination keeps working on
rows loaded or replaced by HTMX.
No build step. No dependencies. One <script> tag.
See Running the demo for a quick way to give it a test drive.
tablekit.js is the only file you need; drop it into your project, add a
<script> tag, and put data-table on any table:
<script src="tablekit.js" defer></script>
<div data-table-ui>
<input type="search" placeholder="Search..." data-table-search />
<label>
Page size
<select data-table-page-size>
<option>10</option>
<option selected>25</option>
<option>50</option>
<option>100</option>
</select>
</label>
<div>
<button type="button" data-table-prev>Prev</button>
<span data-table-status>Page 1</span>
<button type="button" data-table-next>Next</button>
</div>
</div>
<table data-table>
<thead>
<tr>
<!-- data-key: identifier for column; data-type: how to compare if no data-sort -->
<th role="button" tabindex="0" data-key="code" data-type="text">Code</th>
<th role="button" tabindex="0" data-key="amount" data-type="number">Amount</th>
<th role="button" tabindex="0" data-key="startDate" data-type="date">Start Date</th>
<th></th>
</tr>
</thead>
<tbody>
<!-- Searchable values can be supplied on the row or the cell -->
<tr data-search="A-0021 acme corp 2026-01-25 1234.50">
<!-- A cell's contents are used for sorting/searching if no data-sort or data-search is present -->
<td>A-0021</td>
<!-- Number display is formatted; sort is numeric -->
<td data-sort="1234.5">$1,234.50</td>
<!-- Date display is formatted; sort is ISO -->
<td data-sort="2026-01-25">Jan 25, 2026</td>
<td><button type="button">Edit</button></td>
</tr>
<!-- ... more rows ... -->
</tbody>
</table>- Prefers
td[data-sort]if present; otherwise falls back to the cell's text.- Numbers: render
data-sort="1234.5"even if you display$1,234.50. - Dates: render
data-sort="2026-01-25"(ISO) even if you displayJan 25, 2026. - This guarantees correct sorting regardless of locale formatting.
- Numbers: render
- If the corresponding
<th>declaresdata-type="number"ordata-type="date", the sorter parses values accordingly; otherwise it performs text comparison. - Omit
data-keyon the<th>to make that column non-sortable.
- If
tr[data-search]is present, it is used as the row's searchable text (fastest). - Otherwise each cell contributes either
td[data-search](if present) or its text content. - Columns or individual cells can be excluded with
th[data-no-search]andtd[data-no-search].
- A "Loading" overlay appears automatically during any HTMX request whose swap
involves a
data-table(or originates inside[data-table-ui]), if it takes longer than ~300ms. It stays up until the new table finishes initializing. - To show the indicator somewhere specific instead of the overlay, put an element
with
[data-table-loading](and thehiddenattribute) anywhere on the page; tablekit toggles itshiddenattribute instead of injecting the overlay.
- Add
hx-disableto the<tbody>so HTMX skips wiringhx-*attributes on every row at swap time. tablekit removes the attribute and processes only the currently visible page of rows, which is dramatically faster. - Row search text is computed lazily on the first search keystroke, not at init.
| Attribute | On | Purpose |
|---|---|---|
data-table |
<table> |
Marks a table for tablekit to enhance |
data-table-ui |
container | Wraps the search / page-size / pager controls for a table |
data-table-search |
<input> |
Search box |
data-table-page-size |
<select> |
Page size selector |
data-table-prev / data-table-next |
<button> |
Pager buttons |
data-table-status |
element | Receives "Page X / Y (N items)" text |
data-table-loading |
element | Optional custom loading indicator (toggled instead of the overlay) |
data-key |
<th> |
Column identifier; omit to make the column non-sortable |
data-type |
<th> |
text (default), number, or date - how to compare when no data-sort |
data-no-search |
<th> / <td> |
Exclude a column or cell from search |
data-sort |
<td> |
Machine value used for sorting (e.g. 1234.5, ISO date) |
data-search |
<tr> / <td> |
Machine value used for searching |
hx-disable |
<tbody> |
Large-table optimization (see above) |
The demo loads table rows from separate .html files via HTMX, and browsers block
those requests over file:// so the folder must be served over HTTP.
On Windows, double-click demo/serve.bat: it copies tablekit.js into the
demo folder, starts a Python HTTP server on port 8000, and opens the demo in your
browser. To start a server yourself instead, run one of these from demo/:
python -m http.server 8000 # Python
npx serve . # or NodeThen open http://localhost:8000/.
MIT © 2026 Jason Hunt