Changelog
Latest updates and release notes for Niko Table.
April 2026
Section titled “April 2026”Bug Fixes
Section titled “Bug Fixes”- Virtualized table — column spacing no longer compresses on wide datasets —
DataTableVirtualizedBody’s column-lockuseLayoutEffectnow setstableEl.style.minWidthto the sum of all visiblecolumn.getSize()values before measuring column widths. Without this, thew-fullstyle on<table>forced the element to fit its scroll container, causing the auto-layout algorithm to distribute compressed widths and lock them in permanently. SettingminWidthlets the table expand beyond the container width sooverflow-autoon the container handles horizontal scrolling, while auto-layout distributes space based on actual content. TheminWidthis cleared on every unlock/reset cycle so toggling column visibility re-measures correctly. Explicitsizevalues on column defs are respected — they raise theminWidthfloor proportionally.
Examples
Section titled “Examples”- All product-based examples enriched with four new columns —
brand(text),rating(1–5 with ★),revenue(price × stock, formatted withtoLocaleString), andreleaseDate(toLocaleDateString) added to all eight product example files:basic,basic-state,infinite-scroll-table,infinite-scroll-table-state,infinite-scroll-virtualized-table,infinite-scroll-virtualized-table-state,virtualization-table, andvirtualization-table-state. The four infinite-scroll files use index-based deterministic generation (noMath.random) so the values are stable across SSR and client hydration. The two virtualization files retainMath.randomfor variety, consistent with their existing approach. The two state-variant files also gain a BrandDataTableFacetedFilterin the toolbar alongside the existing Status filter.
New Components
Section titled “New Components”DataTableLoadingMore— Composable “loading more” row for infinite-scroll standard tables. Self-gates on itsisFetchingprop — renders a spinner + label when true, nothing when false. Drop it as a child ofDataTableBodyalongsideDataTableSkeletonandDataTableEmptyBody.DataTableVirtualizedLoadingMore— Virtualized variant. Sits outside the virtualizer’s row count so it doesn’t affectestimateSizemath.
New Features
Section titled “New Features”onNearEnd+prefetchThresholdonDataTableVirtualizedBody— Virtualizer-index-driven prefetch trigger for infinite scroll. Fires when the last rendered virtual row is withinprefetchThresholdrows (default 10) of the end of the dataset. Strictly better thanonScrolledBottomfor infinite scroll — catches fast scrolls, scrollbar drag,scrollToIndex()jumps, and initial renders where data doesn’t fill the viewport. Called at most once per false→true transition.
Performance & Layout
Section titled “Performance & Layout”- Virtualized table — native
<table>layout with measure-and-lock column sizing — Replaceddisplay: block/flexon virtualized table rows and cells with native table layout. The browser’s table layout algorithm now distributes column widths based on content.DataTableVirtualizedBodyuses auseLayoutEffectto measure each<th>’s computed width after first data render, then locks the table totable-layout: fixedso columns stay stable during virtual scroll. The effect runs every render but the fast path (already locked, same column count) is two ref reads — negligible at 60 fps. Column visibility changes (toggle, reorder) trigger a fresh measurement cycle. Spacer rows use<tr><td colSpan={n}>to stay within the table layout context. All child components (DataTableVirtualizedEmptyBody,DataTableVirtualizedSkeleton,DataTableVirtualizedLoading,DataTableVirtualizedLoadingMore) updated to match.
Bug Fixes
Section titled “Bug Fixes”- Scroll listener —
onScrolledBottom/onScrolledTopsilently dead — In all four body components (DataTableBody,DataTableVirtualizedBody, and both virtualized DnD bodies), the scroll-listener effect early-returned on!onScroll, which meantonScrolledBottom/onScrolledTopcallbacks were never wired unless the consumer also passedonScroll. The listener now attaches whenever any of the three callbacks is provided, andonScrollis invoked conditionally.
Documentation
Section titled “Documentation”- Infinite Scroll Table — New example page with full implementation guide for non-virtualized infinite scroll (500-row mock,
onScrolledBottom+DataTableLoadingMore). Includes a caution callout recommending the virtualized variant for most use cases. - Infinite Scroll Virtualized Table — New example page for virtualized infinite scroll (5,000-row mock,
onNearEnd+DataTableVirtualizedLoadingMore). Includes comparison table ofonNearEndvsonScrolledBottom, TanStack Query wiring pattern, and controlled-state variant. - Core overview — Added
DataTableLoadingMore,DataTableVirtualizedLoadingMore,onNearEnd, andprefetchThresholdto the component and props reference.
Bug Fixes
Section titled “Bug Fixes”- Faceted filter —
dynamicCountssilently ignored in key paths — The prop was accepted onDataTableFacetedFilteranduseFacetedOptionsbut the fallbackuseMemoand the auto-generated branch ofuseGeneratedOptionsboth computed counts from the wrong row set. SettingdynamicCounts: falsewhile keepinglimitToFilteredRows: truehad no effect. Counts now come fromcountSourceRowsin every branch, and theuseMemodeps includedynamicCountsso React recomputes when it changes. - Faceted filter — explicit
optionsbypassed count enrichment entirely — When a caller passed staticoptionstoDataTableFacetedFilterorTableColumnFacetedFilterMenu, both components short-circuited and returned the caller array untouched.dynamicCountswas a no-op on that path, so multi-select filters with static options never showed live counts, and the single-select path only filtered options by presence without populatingcount. Both entry points now enrich caller-supplied options through the sameoptionRows/countRowssplit used for auto-generated options, and honorlimitToFilteredRowsandshowCountson both the fast and slow paths. - Faceted filter —
TableColumnFacetedFilterMenudroppeddynamicCountson the way to the hook — The column menu wired the prop into its fallback path but calleduseGeneratedOptionsForColumn({ limitToFilteredRows }), silently omittingdynamicCounts. The base hook always saw the defaulttrue. It now forwards{ limitToFilteredRows, dynamicCounts }, matching the parallel wiring inDataTableFacetedFilter. - Faceted filter — zero-count options no longer disappear — Options discovered in the filtered row set were silently dropped when their count in the count row set was
0. Only visible whenlimitToFilteredRows !== dynamicCounts, but when it surfaced it looked like options were flickering in and out. Fixed by separating option discovery from count computation.
Behavior Changes
Section titled “Behavior Changes”- Faceted filter — multi-select now defaults
limitToFilteredRowstofalse— Single-select keeps the previoustruedefault. A narrowing multi-select facet produces a broken UX: un-checking the last selected value can also remove other still-selected values from the visible list, because their rows were just filtered away. Expressed aslimitToFilteredRows ??= !multiple, so explicit props always win.DataTableFacetedFilter,DataTableFacetedFilterContent, andTableColumnFacetedFilterMenuall apply the default at their component entry points. preservemerge strategy contract clarified —preservereturns user-definedmeta.optionsuntouched (no count injection), respectinglimitToFilteredRowsonly to hide values not present in the current row set. Onlyaugmentinjects counts. An earlier refactor draft blurred the distinction; reverted before shipping.
- First behavioral test suite for the library — Introduced Vitest as the unit/component runner. Added
vitest,@vitejs/plugin-react,@testing-library/react,@testing-library/dom,jsdom, and@vitest/uias dev dependencies. New scripts:pnpm test(run once) andpnpm test:watch. - 23 tests across three files — Every
(limitToFilteredRows × dynamicCounts)permutation on a real TanStack table, thepreserve/augmentmerge-strategy branches, zero-count options, and prop-wiring regressions for bothTableColumnFacetedFilterMenuandDataTableFacetedFilter. Each fix above is pinned by at least one test. Fixture columns declare explicitfilterFns so row filtering actually happens — a subtlety worth noting for future contributors. - GitHub Actions CI workflow —
.github/workflows/ci.ymlrunspnpm test,eslint, andpnpm buildon every push tomainand PR targetingmain. Tracks Node LTS automatically vianode-version: "lts/*". Complements the existingregistry.ymlpost-deploy smoke workflow. - Registry example exercises the new default —
faceted.tsx’s multi-select category filter previously setlimitToFilteredRows={false}explicitly; it now relies on the!multipledefault, so any regression will surface visually on the docs site’s Faceted Filter Table example page.
Documentation
Section titled “Documentation”DataTableFacetedFilter—limitToFilteredRowsdefault now shown as!multiplein the props table with both single-select and multi-select branches described.
March 2026
Section titled “March 2026”Performance & consistency
Section titled “Performance & consistency”- Row click: event delegation — All table body components (standard, virtualized, and DnD variants) now use a single delegated row-click handler instead of one handler per row or per cell. This reduces allocations and keeps behavior consistent across
DataTableBody,DataTableVirtualizedBody,DataTableVirtualizedDndBody,DataTableVirtualizedDndColumnBody,DataTableDndBody, andDataTableDndColumnBody. - DataTableRoot — Global filter and default column-pinning handlers are memoized with
useCallbackso table options stay stable and the table does not re-initialize unnecessarily. - TablePagination — Page size, page input, and prev/next button handlers are memoized with
useCallbackfor stable references when pagination state changes. - DataTableAside — Trigger toggle and close button handlers are memoized with
useCallback.
Documentation
Section titled “Documentation”- DataTableRoot — Docs now list the required
childrenprop and the optionalstateprop for controlled mode. The introduction page also documentsclassName. - Props tables and examples were reviewed for accuracy against the current implementation.
Bug Fixes
Section titled “Bug Fixes”- TablePagination — page input draft state — The page number input now uses local draft state so users can type intermediate values (e.g.
"1"before completing"12") without the table jumping mid-edit. The page index is only committed on blur or Enter; invalid input resets to the current page. - TablePagination — stale page index on page size change —
onPageSizeChangenow receives the recalculated page index (Math.floor((pageIndex * pageSize) / newPageSize)) instead of the stale pre-change index. - Sticky header z-index — Fixed an issue where pinned body cells could overlap the sticky header during scroll. The sticky header container now uses
z-30, above header pinned cells (z-20) and body pinned cells (z-10). - Pinned header cell
topstyle — Addedtop: 0to pinned header cells ingetCommonPinningStylesso they stick vertically when the header is sticky.
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