<baht-table>
Server-side paginated table with a ServiceNow-style column picker. It never slices or fetches; you feed the current page.
Ledger
Toolbar count, selection, sortable headers, numeric column, tinted pills and a row-actions button. Every control emits; the host fetches and sorts. Search and filters live outside the table (e.g. <baht-filter> above it).
<baht-table row-key="id" total="143" selectable="true" row-actions="true"></baht-table>
<script>
const t = document.querySelector('baht-table');
const tone = { food: 'success', transport: 'warning', fun: 'danger', salary: 'info', other: 'neutral' };
t.renderers = {
category: (v) => Object.assign(document.createElement('baht-badge'), { tone: tone[v] ?? 'neutral', label: String(v), dot: false }),
account: (v) => Object.assign(document.createElement('baht-badge'), { tone: 'outline', label: String(v) }),
amount: (v) => {
const el = document.createElement('span');
el.className = v < 0 ? 'baht-text--danger' : 'baht-text--success';
el.textContent = (v < 0 ? '-' : '') + '฿' + Math.abs(v).toLocaleString();
return el;
},
};
t.addEventListener('baht-selection-change', (e) => (t.selected = e.detail.selected));
t.addEventListener('baht-sort-change', (e) => (t.sort = e.detail));
</script>Basic
Listen to baht-page-change and fetch that page yourself.
<baht-table row-key="id"></baht-table>
<script>
const el = document.querySelector('baht-table');
el.columns = {
"name": "Name",
"role": "Role",
"status": "Status"
};
el.items = [
{
"id": 1,
"name": "Somchai",
"role": "Admin",
"status": "active"
},
{
"id": 2,
"name": "Malee",
"role": "Editor",
"status": "active"
},
{
"id": 3,
"name": "Prasert",
"role": "Viewer",
"status": "invited"
}
];
el.page = 1;
el.totalPages = 4;
</script>Dot-walking into reference columns
Open Columns, expand Requested by, and add Manager, Name. The header reads "Requested by › Manager › Name" and the cell resolves the path. OK emits baht-columns-change; feed detail.visibleColumns back to remember the layout.
<baht-table row-key="number"></baht-table>
<script>
const el = document.querySelector('baht-table');
el.columns = {
"number": "Number",
"state": "State",
"requester": {
"label": "Requested by",
"fields": {
"name": "Name",
"department": "Department",
"manager": {
"label": "Manager",
"fields": {
"name": "Name",
"title": "Title"
}
}
}
}
};
el.visibleColumns = [
"number",
"requester.name",
"requester.department",
"state"
];
el.items = [
{
"number": "REQ-1041",
"state": "approval",
"requester": {
"name": "Alice Tanaka",
"department": "IT Operations",
"manager": {
"name": "Nok Srisuk",
"title": "Head of IT"
}
}
},
{
"number": "REQ-1042",
"state": "open",
"requester": {
"name": "Chai Boonmee",
"department": "Finance",
"manager": {
"name": "Prasert Wong",
"title": "CFO"
}
}
}
];
el.showIndex = false;
</script>Cursor pagination
No total count: the footer is Prev/Next and baht-cursor-change tells you which way to fetch.
<baht-table cursor-mode="true" has-next="true" row-key="id"></baht-table>
<script>
const el = document.querySelector('baht-table');
el.columns = {
"id": "Id",
"name": "Name"
};
el.items = [
{
"id": "c_01",
"name": "Somchai"
},
{
"id": "c_02",
"name": "Malee"
}
];
</script>Loading skeleton
<baht-table loading="true" skeleton-rows="3"></baht-table>
<script>
const el = document.querySelector('baht-table');
el.columns = {
"name": "Name",
"role": "Role"
};
</script>Empty + error states
<baht-table empty-message="No users yet. Invite one."></baht-table>
<script>
const el = document.querySelector('baht-table');
el.columns = {
"name": "Name"
};
el.items = [];
</script>| Name | Type | Default | Description |
|---|---|---|---|
itemsJS | Record<string, unknown>[] | [] | Rows of the CURRENT page |
columnsJS | ColumnMap | {} | key → header, or { label, fields } for a reference column the picker can dot-walk into (fields nest). Empty = derive from the first row; nested objects become reference columns. |
loading | boolean | false | Skeleton rows |
error | boolean | false | Error state |
error-message | string | "Couldn't load data. Try again." | Error text |
empty-message | string | 'No records yet.' | Empty text |
show-index | boolean | true | Row-number column |
index-offset | number | 0 | Add to row numbers (page offset) |
skeleton-rows | number | 10 | Skeletons while loading |
row-key | string | '' | Row property used as id in baht-row-click |
clickable | boolean | true | Rows clickable |
renderersJS | Record<string, CellRenderer> | {} | Custom cell renderers by key or dotted path (JS only) |
visibleColumnsJS | string[] | [] | Subset + order of columns to render; dotted keys allowed (requester.department). Empty = all top-level columns |
column-picker | boolean | true | Built-in "Columns" button opening the Available/Selected picker. Set the JS property to false to hide it |
total | number | undefined | undefined | Total matching rows across pages; shows "{n} results" in the toolbar |
selectable | boolean | false | Checkbox column + select-all header; controlled by `selected` |
selectedJS | unknown[] | [] | Selected row keys (row-key values, or row indexes without one) |
sortable | boolean | false | Every header becomes a sort button (or set sortable per column) |
sortJS | SortState | null | null | Current sort { key, direction }; mirrored as aria-sort |
row-actions | boolean | false | Trailing "…" button per row; emits baht-row-actions |
page | number | 1 | Current page |
total-pages | number | 1 | |
cursor-mode | boolean | false | Prev/Next footer instead of page numbers (cursor-based APIs) |
has-next | boolean | false | cursor-mode only |
has-prev | boolean | false | cursor-mode only |
totalPages | number | 1 | Total pages |
Column specs accept numeric (right-aligned tabular figures), align (start | center | end) and sortable alongside label and fields.
Events
Section titled “Events”| Event | detail | Description |
|---|---|---|
name | CustomEvent | |
baht-row-click | { row, index, id? } | Row activated |
baht-page-change | { page } | User wants another page. Fetch it |
baht-cursor-change | { direction: 'next'|'prev' } | cursor-mode: fetch that direction |
baht-columns-change | { visibleColumns: string[], columns: { key, label }[] } | Picker confirmed with OK. Persist visibleColumns and feed it back |
baht-selection-change | { selected: unknown[], rows } | Checkbox toggled or select-all. Feed `selected` back |
baht-sort-change | { key, direction: 'asc'|'desc' } | Header clicked. Refetch sorted and feed `sort` back |
baht-row-actions | { row, index, id? } | Row "…" button pressed. Open your menu |