kt-table
A data table: tri-state sorting, single or multiple selection, paging.
Preview
A data table: tri-state sorting, single or multiple selection, paging.
Sorting
Sortable headers are buttons, not clickable <th>s. A header you can only
sort with a mouse is a table half the users cannot sort.
Each press cycles: unsorted → ascending → descending → unsorted. The third press restoring the original order is what makes sorting undoable.
Text sorts through Intl.Collator with numeric: true, in the table's
locale, else the page's <html lang>, else the browser's language. Each
language files accents its own way — "Ångström" next to "Angstrom" in English,
after "Zeta" in Swedish — and "Entity 2" comes before "Entity 10". Empty values sort to the end whichever way the column points — a
descending sort should not open with a screen of blanks.
aria-sort on the header reflects the state.
The rows are sorted once per change of data or of the order, not on every
render, so hovering and selecting stay quick on a few thousand rows. That relies
on data being replaced, not edited: assign a new array
(table.data = [...rows, added]) rather than pushing into the old one.
Server-side data
With manual, the table sorts and pages nothing itself: data is the page the
server sent, shown as it is, and total-rows is how many rows the server holds,
which the pager counts from. Headers and the pager still update sortKey,
sortDirection and page and fire their events; the application loads what
they ask for.
A new sort sends page back to 1 and fires only kt-sort-change — one event,
one reload. While loading, a table that already shows a page keeps it on
screen, dimmed and aria-busy, so the pager that asked for the next one keeps
its place and its focus; the loading message only replaces the table before
anything has loaded.
Selection is kept by row identity, so it survives paging. selectedRows can
only return the selected rows of the page on screen: keep the rows themselves
on your side if you need them across pages.
Thousands of rows
virtual renders only the rows in view — a few dozen, whatever the length of
data — so five thousand rows scroll as smoothly as fifty, without paging. The
table then scrolls inside its own box, under a header that stays on top: give
it a height.
Every row must be the same height. The first one rendered is measured, so
compact and your own padding are followed, but a custom cell taller than the
rest would put the scroll position out of step with the rows.
Sorting and selection work on the data, not on what is rendered: the header box
selects every row, not only those on screen. aria-rowcount and
aria-rowindex let a screen reader announce "row 1,204 of 5,000" with thirty
rows in the page. A page-size takes precedence over virtual.
Selection
Rows are identified by id, then uid, then the row object itself. selected
holds those identities; selectedRows gives you the rows.
Multiple mode adds a select-all box in the header, which goes indeterminate on a partial selection.
Custom cells
renderCell returns anything Lit can render — or undefined to fall back to
the raw value, so it only has to handle the columns it cares about.
API
| Property | Attribute | Type | Default |
|---|---|---|---|
columns | — | KtTableColumn[] | [] |
data | — | KtTableRow[] | [] |
selectable | selectable | boolean | false |
selectionMode | selection-mode | 'single' | 'multiple' | 'multiple' |
selected | — | unknown[] | [] |
pageSize | page-size | number (0 = no paging) | 0 |
page | page | number, from 1 | 1 |
manual | manual | boolean | false |
totalRows | total-rows | number (manual mode) | 0 |
virtual | virtual | boolean | false |
loading | loading | boolean | false |
compact | compact | boolean | false |
sortKey | sort-key | string | null | null |
sortDirection | sort-direction | 'asc' | 'desc' | null | null |
renderCell | — | (row, column) => unknown | — |
emptyText | empty-text | string | 'No data to display' |
loadingText | loading-text | string | 'Loading…' |
label | label | string | '' |
locale | locale | string (BCP 47) | '' (page, then browser) |
| Getter | Returns |
|---|---|
sortedRows | All rows, sorted, before paging |
visibleRows | The rows currently on screen |
selectedRows | The selected rows themselves |
totalPages | Page count |
| Event | Detail |
|---|---|
kt-sort-change | { key, direction } |
kt-selection-change | { selected, rows } |
kt-row-click | { row, index } |
kt-page-change | { page } |
| Part | Description |
|---|---|
table | The <table> |
header-cell | A <th> |
row | A <tr> |
cell | A <td> |