Table
stable 14.2 kB<hk-table>The one component that has to do everything: hierarchical headers of any depth, per-column and global filtering, single or multi-column sort, client or server paging, selection, row expansion, row grouping, frozen columns, resize, reorder, column visibility, inline editing, footer aggregates, virtual scrolling, CSV export and persisted state. It renders through delegated events and a windowed body, so ten thousand rows scroll at frame rate instead of turning into a slideshow.
Live demo
Running in this page — interact with it.
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
| value | T[] | [] | Rows to render. With [lazy] this is the current page only. |
| columns | HkColumn<T>[] | [] | Column tree. Nest children for grouped headers of any depth. |
| dataKey | string | '' | Unique row field. Required for stable selection, expansion and editing. |
| loading | boolean | false | Shows the loading overlay and sets aria-busy. |
| lazy | boolean | false | Hands sorting, filtering and paging to the server through (lazyLoad). |
| totalRecords | number | null | null | Server-side row count. Only read when lazy. |
| paginator | boolean | false | Renders the pager and slices the data client-side. |
| first / rows | number | 0 / 10 | Page offset and page size. Both two-way bindable. |
| rowsPerPageOptions | number[] | [10, 25, 50, 100] | Page-size choices. Pass [] to hide the selector. |
| sortMode | 'single' | 'multiple' | 'single' | In multiple mode, shift-click stacks sort fields. |
| sort | HkSortMeta[] | [] | Active sort stack, outermost first. Two-way bindable. |
| filters | HkFilterState | {} | Per-column filter state, keyed by dotted column id. |
| globalFilter | string | '' | Search term. Two-way bindable; debounced by searchDelay. |
| globalFilterFields | string[] | [] | Fields the search looks at. Defaults to every visible leaf. |
| selectionMode | 'single' | 'multiple' | 'checkbox' | null | null | How rows are picked. Checkbox adds a leading column with select-all. |
| selection | T[] | [] | Selected rows — always an array, even in single mode. Two-way bindable. |
| rowExpansion | boolean | false | Adds the expander column; pair with an "expansion" template. |
| expandedKeys | Record<string, boolean> | {} | Open rows, keyed by dataKey. Two-way bindable. |
| groupRowsBy | string | '' | Field to group rows by. Groups render contiguously. |
| rowGroupMode | 'subheader' | 'rowspan' | 'subheader' | Collapsible group header row, or a merged cell spanning the group. |
| showGroupFooter | boolean | false | Per-group subtotal row using each column aggregate. |
| virtualScroll | boolean | false | Windows the body to the visible rows. Needs a scrollHeight. |
| virtualRowHeight | number | 36 | Row height in px. Rows are pinned to it so the spacers stay exact. |
| scrollHeight | string | '' | Max height of the scroll viewport, e.g. "24rem". |
| stickyHeader | boolean | true | Pins every header row, offsetting each by the ones above it. |
| resizableColumns | boolean | false | Drag a header edge to resize. Switches the table to fixed layout. |
| reorderableColumns | boolean | false | Drag top-level headers to reorder them. |
| columnToggle | boolean | false | Adds the column-visibility menu to the toolbar. |
| editMode | 'cell' | 'row' | null | null | Cell edits on double-click or Enter; row mode adds Save/Cancel controls. |
| size | 'sm' | 'md' | 'lg' | 'md' | Density. Drives padding and font size off one token. |
| striped / gridlines / hover | boolean | false / true / true | Chrome toggles. |
| showIndex | boolean | false | Serial-number column, continuous across pages. |
| showSearch / showExport | boolean | false | Toolbar search box and CSV export button. |
| rowClass | string | ((row: T, i: number) => string) | '' | Extra classes per row — threshold highlighting and the like. |
| keyboardNavigation | boolean | true | Arrow-key cell focus, Space to select, Enter to edit. |
| stateKey | string | '' | Persists sort, filters, page, widths, order and visibility under this key. |
Outputs
lazyLoadEventEmitter<HkTableLazyEvent>Server needs a page. Carries first, rows, sort, filters and the trigger.
pageChangeEventEmitter<HkPageEvent>Page or page size changed.
sortChangeEventEmitter<HkSortMeta[]>The sort stack after the click.
rowSelect / rowUnselectEventEmitter<{ row, index }>One row moved in or out of the selection.
rowExpand / rowCollapseEventEmitter<{ row, key }>Detail panel opened or closed.
editComplete / editCancelEventEmitter<HkEditEvent<T>>An edit was committed or abandoned. Carries value and oldValue.
columnResize / columnReorderEventEmitter<…>Column geometry changed — persist it yourself, or use stateKey.
Methods
reset()() => voidClears sort, filters, paging and every column override.
clearFilters()() => voidDrops all column filters and the search term.
exportCsv()() => voidExports the filtered, sorted set — not just the visible page.
expandAll() / collapseAll()() => voidBulk row expansion.
refresh()() => voidRe-runs the pipeline, or re-asks the server when lazy.
Keyboard
- ← ↑ → ↓ Move cell focus
- Home / End First / last column in the row
- Space Toggle selection of the focused row
- Enter Start editing the focused cell
- Escape Abandon the edit and restore the old value
- Shift + click header Add the column to the sort stack (multiple mode)
Theming
Component tokens, each falling back to a global one:
| Property | Default | Controls |
|---|---|---|
| --hk-table-header-bg | #121214 | Header row background. Sticky rows inherit it. |
| --hk-table-row-hover | rgb(255 255 255 / 0.04) | Row hover wash. |
| --hk-table-stripe | rgb(255 255 255 / 0.02) | Even-row tint when [striped]. |
| --hk-table-selected | rgb(220 38 38 / 0.16) | Selected-row background. |
| --hk-table-density | 1 | Multiplier on cell padding. [size] sets it; override for anything between. |
| --hk-table-cell-x | 0.7rem | Horizontal cell padding. |
| --hk-table-frozen-shadow | 8px 0 12px -10px rgb(0 0 0 / 0.85) | Edge shadow that separates a frozen column from the scrolling body. |
| --hk-table-panel-bg | #141416 | Background for the filter and column-toggle panels. |
Accessibility
- Renders role="grid" with aria-rowcount and aria-colcount reflecting the full set, not the page.
- Sortable headers expose aria-sort; the sort order badge is readable text, not colour alone.
- Grouped headers use scope="colgroup" with derived colspan/rowspan, so the reading order matches the visual one.
- One tab stop per grid: the focused cell is the only tabbable one, arrows do the rest.
- The loading overlay is role="status" aria-live="polite"; the grid itself sets aria-busy.