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.

#grid#datatable#virtual#filter#sort
In the package npm i hellskitchen-ui Import hk-table and use it directly.

Live demo

Running in this page — interact with it.

Inputs

NameTypeDefaultDescription
valueT[][]Rows to render. With [lazy] this is the current page only.
columnsHkColumn<T>[][]Column tree. Nest children for grouped headers of any depth.
dataKeystring''Unique row field. Required for stable selection, expansion and editing.
loadingbooleanfalseShows the loading overlay and sets aria-busy.
lazybooleanfalseHands sorting, filtering and paging to the server through (lazyLoad).
totalRecordsnumber | nullnullServer-side row count. Only read when lazy.
paginatorbooleanfalseRenders the pager and slices the data client-side.
first / rowsnumber0 / 10Page offset and page size. Both two-way bindable.
rowsPerPageOptionsnumber[][10, 25, 50, 100]Page-size choices. Pass [] to hide the selector.
sortMode'single' | 'multiple''single'In multiple mode, shift-click stacks sort fields.
sortHkSortMeta[][]Active sort stack, outermost first. Two-way bindable.
filtersHkFilterState{}Per-column filter state, keyed by dotted column id.
globalFilterstring''Search term. Two-way bindable; debounced by searchDelay.
globalFilterFieldsstring[][]Fields the search looks at. Defaults to every visible leaf.
selectionMode'single' | 'multiple' | 'checkbox' | nullnullHow rows are picked. Checkbox adds a leading column with select-all.
selectionT[][]Selected rows — always an array, even in single mode. Two-way bindable.
rowSelectable(row: T, i: number) => booleannullWhich rows may be selected. A rejected row is skipped by click, by the space key and by select-all alike — a checkbox that renders enabled then refuses to tick is worse than one never offered.
selectAllScope'page' | 'filtered''page'What the header checkbox covers. `filtered` ticks every row the filters match across all pages, which is what "select all" usually means on page 3 of 40.
rangeSelectionbooleantrueShift-click selects the span between the last selection and the click, across page boundaries.
frozenRowsT[][]Rows pinned above the scrolling body — a totals line, a row being entered. Separate from `value`, so they never sort, filter or page away, and they honour frozen columns too.
responsiveLayout'scroll' | 'stack''scroll'Below `stackBreakpoint`, `stack` turns each row into a card with every cell labelled by its column. Measured off the grid’s own box, so one in a narrow sidebar stacks while the page around it stays wide.
stackBreakpointnumber640Width in px below which `stack` takes over.
defaultSortOrder1 | -11Direction a column takes on its first click. Descending-first suits a “newest” or “largest” column.
resetPageOnSortbooleantrueWhether sorting returns to page one. Off keeps the reader’s offset.
customSort(rows: T[], sort: HkSortMeta[]) => T[]nullReplaces the whole sort step, for orderings no per-column comparator can express.
csvSeparatorstring','Field separator for the export. Locales with a comma decimal mark need `;` — Excel there splits on `;`, and a comma-separated file opens as one column per row.
exportFormat(value, column, row) => stringnullReplaces the cell-to-text step for the export.
rowExpansionbooleanfalseAdds the expander column; pair with an "expansion" template.
expandedKeysRecord<string, boolean>{}Open rows, keyed by dataKey. Two-way bindable.
groupRowsBystring''Field to group rows by. Groups render contiguously.
rowGroupMode'subheader' | 'rowspan''subheader'Collapsible group header row, or a merged cell spanning the group.
showGroupFooterbooleanfalsePer-group subtotal row using each column aggregate.
virtualScrollbooleanfalseWindows the body to the visible rows. Needs a scrollHeight.
virtualRowHeightnumber36Row height in px. Rows are pinned to it so the spacers stay exact.
scrollHeightstring''Max height of the scroll viewport, e.g. "24rem".
stickyHeaderbooleantruePins every header row, offsetting each by the ones above it.
resizableColumnsbooleanfalseDrag a header edge to resize. Switches the table to fixed layout.
reorderableColumnsbooleanfalseDrag top-level headers to reorder them.
columnTogglebooleanfalseAdds the column-visibility menu to the toolbar.
editMode'cell' | 'row' | nullnullCell 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 / hoverbooleanfalse / true / trueChrome toggles.
showIndexbooleanfalseSerial-number column, continuous across pages.
showSearch / showExportbooleanfalseToolbar search box and CSV export button.
rowClassstring | ((row: T, i: number) => string)''Extra classes per row — threshold highlighting and the like.
keyboardNavigationbooleantrueArrow-key cell focus, Space to select, Enter to edit.
stateKeystring''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()() => void

    Clears sort, filters, paging and every column override.

  • clearFilters()() => void

    Drops all column filters and the search term.

  • exportCsv()() => void

    Exports the filtered, sorted set — not just the visible page.

  • expandAll() / collapseAll()() => void

    Bulk row expansion.

  • refresh()() => void

    Re-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:

PropertyDefaultControls
--hk-table-header-bg#121214Header row background. Sticky rows inherit it.
--hk-table-row-hoverrgb(255 255 255 / 0.04)Row hover wash.
--hk-table-stripergb(255 255 255 / 0.02)Even-row tint when [striped].
--hk-table-selectedrgb(220 38 38 / 0.16)Selected-row background.
--hk-table-density1Multiplier on cell padding. [size] sets it; override for anything between.
--hk-table-cell-x0.7remHorizontal cell padding.
--hk-table-frozen-shadow8px 0 12px -10px rgb(0 0 0 / 0.85)Edge shadow that separates a frozen column from the scrolling body.
--hk-table-frozen-row-bgvar(--hk-table-header-bg)Fill behind a pinned row.
--hk-table-frozen-row-shadow0 4px 8px -6px rgb(0 0 0 / .35)Seam under the pinned block, so it reads as pinned rather than as the first data row.
--hk-table-panel-bg#141416Background 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.

More in Data