Semantic table enhancement for Oat UI. Sort, filter, and select rows without turning tables into a framework widget.
Oat Table is a small, zero-runtime-dependency extension for Oat. It keeps the native <table> as the source of truth and adds progressive behavior around it:
- sortable columns via
th[data-sort] - client-side filtering via
[data-table-filter] - row selection via native checkboxes
- empty and status output helpers
- Oat theme variables and component layers
The table remains readable and usable without JavaScript. With JavaScript enabled, <ot-table> wires up keyboard-friendly sort buttons, ARIA sort state, filter state, selection state, and events.
With npm:
npm install @globus.studio/oat-tableimport '@globus.studio/oat-table/css';
import '@globus.studio/oat-table';From a CDN:
<link rel="stylesheet" href="https://oat.ink/oat.min.css">
<link rel="stylesheet" href="https://unpkg.com/@globus.studio/oat-table@0.1.0/dist/oat-table.min.css">
<script src="https://oat.ink/oat.min.js" defer></script>
<script src="https://unpkg.com/@globus.studio/oat-table@0.1.0/dist/oat-table.min.js" defer></script><ot-table>
<form data-table-toolbar role="search">
<label>
Search
<input type="search" data-table-filter placeholder="Filter rows">
</label>
<output data-table-status aria-live="polite"></output>
</form>
<div class="table">
<table>
<thead>
<tr>
<th scope="col" data-sort>Name</th>
<th scope="col" data-sort="number">Open tickets</th>
<th scope="col" data-sort="date">Last seen</th>
</tr>
</thead>
<tbody>
<tr>
<td>Ada Lovelace</td>
<td>4</td>
<td><time datetime="2026-05-15">15 May 2026</time></td>
</tr>
<tr>
<td>Grace Hopper</td>
<td>12</td>
<td><time datetime="2026-05-10">10 May 2026</time></td>
</tr>
</tbody>
</table>
</div>
</ot-table>Add data-sort to a header cell. Oat Table wraps the header content in a plain button and updates aria-sort on the active header.
<th scope="col" data-sort>Name</th>
<th scope="col" data-sort="number">Revenue</th>
<th scope="col" data-sort="date">Created</th>Supported sort types are text, number, date, and auto. Empty data-sort defaults to text.
Use data-sort-value on a cell when the visible text is formatted:
<td data-sort-value="42128">$42,128</td>Place an input with data-table-filter anywhere inside <ot-table>.
<input type="search" data-table-filter placeholder="Filter rows">
<output data-table-status aria-live="polite"></output>Filtering matches the row text. Use data-filter-text on a row to provide a custom searchable string.
<tr data-filter-text="ada admin active london">
...
</tr>Set a custom empty message on the wrapper:
<ot-table empty-text="No customers match this search">Selection uses native checkboxes. This keeps forms and accessibility straightforward.
<th scope="col"><input type="checkbox" data-table-select-all aria-label="Select all visible rows"></th>
<td>
<input type="checkbox" data-table-select-row value="customer-1" aria-label="Select Ada Lovelace">
</td>Add an optional selected count output:
<output data-table-selected aria-live="polite"></output>The select-all checkbox affects visible rows only, which makes it work naturally with filtering.
| Attribute | Element | Description |
|---|---|---|
data-sort |
th |
Enables sorting. Values: text, number, date, auto. |
data-sort-value |
td |
Explicit value used for sorting. |
data-table-filter |
input |
Filters rows by text. |
data-filter-text |
tr |
Custom text used for filtering a row. |
data-table-status |
output |
Receives visible row count. |
data-table-selected |
output |
Receives selected row count. |
data-table-select-all |
input[type=checkbox] |
Toggles visible row checkboxes. |
data-table-select-row |
input[type=checkbox] |
Marks a row as selectable. |
empty-text |
ot-table |
Custom empty state text. |
const table = document.querySelector('ot-table');
table.addEventListener('ot-table-sort', (event) => {
console.log(event.detail.column, event.detail.direction);
});
table.addEventListener('ot-table-filter', (event) => {
console.log(event.detail.query, event.detail.visibleRows.length);
});
table.addEventListener('ot-table-select', (event) => {
console.log(event.detail.selectedValues);
});If you add or remove rows dynamically, call refresh() on the component or dispatch ot-table-refresh.
document.querySelector('ot-table').refresh();npm install
npm run checknpm run check builds dist/ and runs the DOM test suite.
npm run check
npm publish --dry-run
npm publishThe package is configured for public scoped npm publishing with publishConfig.access.
MIT.
