Changelog
Latest updates and release notes for Niko Table.
May 2026
Section titled “May 2026”getRowMemoKeyprop — all six body components (DataTableBody,DataTableVirtualizedBody,DataTableDndBody,DataTableDndColumnBody,DataTableVirtualizedDndBody,DataTableVirtualizedDndColumnBody) now acceptgetRowMemoKey?: (row: TData) => string. Return a string per row that encodes external row-level state (inline edit draft values, optimistic overlays, saving spinners). When the string changes for a row, React.memo re-renders only that row — without this, closures captured in columncelldefinitions become stale. See the Inline Edit Table example.DataTableVirtualizedFlexHeader— flex-layout header forDataTableVirtualizedDndBody. Pick by body: plain →DataTableVirtualizedHeader, row-DnD →DataTableVirtualizedFlexHeader, column-DnD →DataTableVirtualizedDndHeader.
Changed
Section titled “Changed”- Options with
count: 0are hidden across every filter UI (faceted filter, filter menu, inline filter). Server-side tables that pass cross-filter counts get automatic narrowing without writing helpers. Pure label-only callers (no counts) unaffected. Opt-out per option by passingcount: undefined. autoResetPageIndexdefaults tofalse(wastrue). Sort/filter changes now preserve the pagination cursor. Opt back in viaconfig={{ autoResetPageIndex: true }}.
TableRangeFilter—[min, max]memo depends on faceted scalars, so the range refreshes on data change.formatValueno longer locale-formats numbers (type="number"inputs reject localized output).TableSliderFilterclear button — alwaysstopPropagation()(was DIV-only, so SVG/icon clicks bubbled and re-opened the popover).- Sort / filter / inline-filter column memos — now key on
table.options.columns(table ref alone is too stable). - Expanded-row re-measure effect —
rowMemoKeyis now in theuseEffectdependency array of virtualized body rows so height is re-measured when inline edit state changes while a row is expanded. TableSortMenu— derives the next sorting fromtable.getState().sortinginstead of the closure-capturedsorting.- CSV export — plain objects are now JSON-encoded instead of serializing as
[object Object]. - Filter menu / inline filter — stale counts on filter changes.
<DataTableFilterMenu />and<DataTableInlineFilter />mutatemeta.optionsto inject counts; once pinned, those counts survived later filter changes so the count-0 hide rule never fired (e.g. selecting Category=Clothing left every Brand option visible). Pristine options are now captured in a ref andmeta.optionsis rebuilt from that source each render — cross-filter narrowing now works in both filter UIs. - Faceted filter — caller-supplied options no longer narrowed to current row set; caller is the source of truth.
TableColumnFacetedFilterMenu—limitToFilteredRowsdecoupled frommultipledefault for caller options.- Cross-table “state update on unmounted component” warning silenced via mount-ref guards on every default state setter.
onRowClickevent type unified toReact.MouseEvent<HTMLElement>across all bodies.- Virtualized DnD body — expanded-row height re-measured on toggle without losing
useSortableregistration. - Virtualized non-DnD body —
setColumnsLockedrace fixed;columnsLockedgatesmeasureElement. - Virtualized bodies —
data-indexusesvirtualRow.index; click delegation via stabledata-row-id+table.getRow(rowId). - Virtualized bodies — expanded-row height included in virtualizer measurement.
- Virtualized Row-DnD body — selected row styling applies (
data-state="selected"). - Virtualized DnD bodies —
onRowClickwidened toReact.MouseEvent<HTMLElement>. - Column-DnD bodies (virtualized + non-virtualized) — expanded-row rendering matched to other bodies.
VirtualizedDraggableRow— addeddata-indexalongsidedata-row-index.FILTER_OPERATORS.RELATIVEhidden from the date-filter dropdown (kept in catalogue for server-side consumers); per-rowconsole.errorthrottled.getObjectHash— full sorted-keys join (was first-3 keys, false-negatived row selection with sequential IDs).
Performance
Section titled “Performance”- Scroll handler rAF-coalesced;
onScrolledTop/onScrolledBottomfire only on the leading edge (false→true). - Row-click guard hardened — suppresses clicks during active text selection; recognizes
textarea,select,label,[contenteditable], and ARIAcombobox/menuitem/textbox. - Slider filters —
facetedMin/facetedMaxhoisted into memo deps so[min, max]stays reactive on data change. TableViewMenuvisible-columns memo keys ontable.options.columns(was just table ref).DataTableBody— row clicks delegated to<tbody>, removing one listener per row.tableOptions— 6 inline fallback setters extracted to stableuseCallbacks; deps trimmed to destructuredstate/initialState/globalFilterFn.expandColumnIdmemoized in all six bodies (was O(rows × cols) per render).DataTableColumnHeaderRootcontext value memoized.useKeyboardShortcutslistener no longer re-attaches every render.onRowSelectionno longer fires on initial mount — user-driven changes only.handleRowSelectionChangehonors fullUpdater<T>contract; side effects moved touseEffect.DataTableLoadingself-gates onisLoading.useGeneratedOptions— eliminated doublegetFilteredRowsExcludingColumnwalk.
Refactor
Section titled “Refactor”- Row-click guard moved to
lib/row-click.ts(was inlined in 6 places); body-scroll listener extracted tolib/create-scroll-handler.ts.
Internal
Section titled “Internal”data-table-virtualized-dnd-structure.tsxmarked@internal.- DnD virtualized bodies don’t gate
measureElementoncolumnsLocked— flex layout has no auto-layout pass.
April 2026
Section titled “April 2026”- Virtualized table — column spacing no longer compresses on wide datasets. The column-lock effect now sets
tableEl.style.minWidthto the sum of visiblecolumn.getSize()values before measuring, so the table can expand beyond its scroll container instead of being squeezed byw-full.
DataTableLoadingMoreandDataTableVirtualizedLoadingMore— composable “loading more” rows for infinite-scroll tables. Self-gate onisFetching.onNearEnd+prefetchThresholdonDataTableVirtualizedBody— virtualizer-index-driven prefetch trigger for infinite scroll. Strictly better thanonScrolledBottom: catches fast scrolls, scrollbar drag,scrollToIndex()jumps, and short initial renders.
Changed
Section titled “Changed”- Virtualized table — switched to native
<table>layout with measure-and-lock column sizing. The browser distributes column widths from content;DataTableVirtualizedBodymeasures each<th>after first data render and locks totable-layout: fixed.
- Scroll listener —
onScrolledBottom/onScrolledTopnow wire up even whenonScrollisn’t provided (was silently dead in all four bodies). - Faceted filter —
dynamicCountshonored on every code path (was silently ignored in fallbackuseMemoand the auto-generated branch). - Faceted filter — caller-supplied
optionsgo through count enrichment instead of short-circuiting. - Faceted filter —
TableColumnFacetedFilterMenuforwardsdynamicCountsto the underlying hook. - Faceted filter — zero-count options no longer disappear.
Changed
Section titled “Changed”- Faceted filter — multi-select defaults
limitToFilteredRowstofalse(single-select keepstrue). Expressed aslimitToFilteredRows ??= !multiple. preservemerge strategy — returns user-definedmeta.optionsuntouched. Onlyaugmentinjects counts.
- Vitest introduced. 23 tests covering every
(limitToFilteredRows × dynamicCounts)permutation, merge-strategy branches, zero-count options, and prop-wiring regressions. - GitHub Actions CI runs
pnpm test,eslint, andpnpm buildon every PR and push tomain.
Documentation
Section titled “Documentation”- New example pages: Infinite Scroll Table (non-virtualized) and Infinite Scroll Virtualized Table.
- Core overview reference adds
DataTableLoadingMore,DataTableVirtualizedLoadingMore,onNearEnd,prefetchThreshold.
March 2026
Section titled “March 2026”Performance
Section titled “Performance”- Row click delegated at
<tbody>instead of per-row across all six body components. DataTableRoot,TablePagination,DataTableAside— handlers memoized withuseCallbackfor stable references.
TablePagination— page input uses local draft state. Page index commits on blur or Enter.TablePagination—onPageSizeChangenow receives the recalculated page index.- Sticky header z-index — pinned body cells no longer overlap the sticky header (
z-30>z-20>z-10). - Pinned header cells get
top: 0so they stick vertically when the header is sticky.
Documentation
Section titled “Documentation”DataTableRoot— docs list the requiredchildrenprop and the optionalstateprop.
February 2026 — Initial Release
Section titled “February 2026 — Initial Release”The first public release of Niko Table — a composable, shadcn-compatible data table component registry built with TanStack Table and React.
Components
Section titled “Components”- DataTable - Core data table with sorting, filtering, and pagination
- DataTableVirtualized - Virtualized rendering for large datasets
- DataTablePagination - Flexible pagination controls
- DataTableSearchFilter - Global search across all columns
- DataTableFacetedFilter - Multi-select faceted filtering with counts
- DataTableFilterMenu - Advanced filter menu with AND/OR logic
- DataTableInlineFilter - Inline filter bar with mixed operator support
- DataTableSliderFilter - Range slider filtering for numeric columns
- DataTableDateFilter - Date and date range filtering
- DataTableExportButton - CSV/JSON export
- DataTableAside - Side panel for row details
- DataTableSelectionBar - Bulk actions on selected rows
- DataTableColumnSort - Sort menu and sort icon components
- DataTableColumnHide - Column visibility toggle
- DataTableColumnPin - Column pinning (left/right)
Features
Section titled “Features”- Row DnD - Drag and drop row reordering via
@dnd-kit - Column DnD - Drag and drop column reordering
- Row Selection - Single and multi-row selection with bulk actions
- Row Expansion - Expandable rows with custom content
- Tree Table - Hierarchical data with expand/collapse
- Virtualization - Smooth scrolling for 10k+ rows
- URL State - Sync filters, sorting, and pagination to URL via
nuqs - Server-Side - Server-side filtering, sorting, and pagination support
Filter System
Section titled “Filter System”- 15+ filter operators (contains, equals, greater than, between, etc.)
- Mixed AND/OR logic with mathematical precedence
- Per-filter join operator control
- Regex-cached filter execution for large datasets