Changelog
Latest updates and release notes for Niko Table.
July 2026
Section titled “July 2026”- Base UI support — all 31 registry items install cleanly into both shadcn generations: Radix (
new-york) and Base UI (base-nova, the currentshadcn initdefault). Dual-generation-safe callbacks and props; verified with fresh-app installs of every block under both styles. - Server-Side Grid — editable grid backed by a server: rows stream in on scroll (no pagination), edits save as a validated
created/updated/deletedchangeset withreconcile. example - Drizzle ORM guide — the server-side wire contract implemented with Drizzle + Postgres, with a live mocked demo that shows the generated SQL as you filter and sort. guide
- Drizzle ORM + Nuqs — the same Drizzle SQL layer with table state in the URL via nuqs (shareable, bookmarkable, refresh-proof). guide
normalizeFiltersFromUrlexported fromfilters/table-filter-menu(counterpart ofserializeFiltersForUrl) for restoring advanced-menu filters from URL or controlled state- Data Grid (
data-table-grid) — composable, virtualized spreadsheet grid:useDataGrid+<DataGrid>+ opt-in children. Introduction - Persistence (
data-table-grid-changes) —useGridChanges→ create / update / delete. Persistence - Inline validation —
CellState.errortooltip on the cell. Validation - Column resize —
<DataTableColumnResize />; drag grips, keyboard nudge, double-click autosize. example - Column fill on by default — the first non-pinned data column absorbs the leftover row width, so tables fill the container and a trailing actions column pins right, with no per-table setup.
meta.flexoverrides which column fills;meta.flex: false/TableMeta.disableFlexFillopt out. Pure layout, never persisted. example - Header-fit — un-resized columns are floored at their header width (measured off-DOM), so a compact column never truncates its label on load.
- Resizable fill column — the fill column resizes via a drag that starts from its rendered width (no jump) and hands the fill to the next column on release.
- Column auto-fit — opt-in
<DataTableColumnAutoFit />marker that spreads leftover space evenly across resizable columns (the alternative to single-column fill); fixed columns keep their size, restored/persisted widths win. example - Column sizing persistence — opt-in
useColumnSizingPersistence(storageKey)hook: wire itscolumnSizing+onColumnSizingChangeinto<DataTableRoot>and resized widths persist tolocalStorage(immediate write, cross-tab sync). Persist Widths - Docs — full registry catalog (resize + Data Grid + axis DnD packages), Install Everything lists all items, homepage/
CliCommandCodenamespace install - Docs — Data Grid intro live demo includes Current Table State panel
- Docs — Manual Installation project tree includes
grid/, column resize, flash/scroll hooks - Docs — Data Grid overview layers + Data Grid Examples (mirrors Table IA)
- Docs — Documentation Guidelines
- Registry —
data-table-virtualized-row-dnd/data-table-virtualized-column-dnd
Changed
Section titled “Changed”- Server-side examples reworked around a database-agnostic wire contract — one serializable query, a fenced
MOCK SERVERsection with operator → SQL mapping, every filter working server-side out of the box, cross-filter facets, mocked demos with bigger datasets. Server-Side Table · Nuqs - Grid examples mount
<DataTableColumnResize />(columns flex-fill the table width) and space toolbar/table withspace-y-2 - Installation notes both shadcn generations are supported (
new-yorkRadix andbase-novaBase UI) - Docs Source/API links use Astro
DocsLink(aligned outline buttons; Starlight MDX stripped ReactButton asChild) - Install CLI yarn tab uses
yarn dlx; Installation page is Prerequisites → Registry → Install first - Skills — Data Grid coverage + Cursor / Claude Code / Windsurf / agents install paths
niko-table-best-practicesskill teaches Data Grid composition- Import DnD from
data-table-row-dnd-structure/data-table-column-dnd-structure(and virtualized twins) DEFAULT_MIN_COLUMN_SIZEmoved tolib/constants(root no longer imports resize-handle for it)enableSortingdefaults off unless config or feature detection enables it- DnD bodies support row context menus; non-virt column-DnD body honors resize; column-DnD example mounts resize
- Column resize applies on drag-end (
columnResizeMode: "onEnd") with a container-level preview line, so memoized rows don’t re-render every mousemove
Removed
Section titled “Removed”data-table-dnd-structure.tsx/data-table-virtualized-dnd-structure.tsx(no re-export shims — use the axis files)
- Fresh installs were broken —
lib/row-click.tsandlib/create-scroll-handler.tswere imported by core files but shipped by no registry item; both now ship withdata-table - Server-side examples: single-date filters (ms timestamps) match server-side; large page sizes scroll inside a
maxHeightcontainer instead of growing the page - Keyboard nudge and double-click autosize exit auto-fit like a drag, so the fit no longer redistributes space a keyboard user just removed
- Header-fit floors follow the rendered title’s fallback (formatted column id), apply only to resizable columns, and skip identity churn when unchanged (no extra full-table re-render on load)
- Resize handle announces the real rendered width for fill/floored columns; an interrupted fill-column drag tears down cleanly on unmount
useColumnSizingPersistenceskips redundant mount re-reads and rejects non-positive stored widthsresolveColumnWidthkeeps a flex column’s declared size when resizing is off (checksresizingbeforeisFlex)- Column-resize grip is idle-invisible (shows on hover/focus/drag) so it doesn’t read as a header divider offset from body
border-r - Body cells truncate on column resize (no text spill into neighbors); default
minSize40px when resize is enabled DataTableDndBodysupports column resize (Row DnD +<DataTableColumnResize />)- DnD contexts use
React.useId()to avoid SSRaria-describedby(DndDescribedBy-N) hydration mismatches - Virtualized row/column DnD supports
<DataTableColumnResize />(flex header + body sizing; grip won’t start a reorder) - Introduction composability map table rendering
- Sidebar scroll restore without Starlight
SidebarPersister(freeze with theme-black) - Core — memoized body rows invalidate on column add/remove (
columnsincolumnLayoutSignature) — dynamic columns no longer leave body cells / borders stale - Core —
data-row-indexis display order (flashRows/ scroll after sort/filter) - Core — empty/loading
colSpanuses visible columns; resize width cleanup restores prior table styles - Core —
daysAgo()is deterministic (fixed epoch) - Validation settle no-ops on identical row refs (no infinite loop)
- Display index under sort/filter (selection, gutter, cross-highlight)
- Blank SSR seed (
row-0…);clearAllkeeps seed row count - Scroll-into-view with right-pinned columns
- Undo/redo / filter / hide no longer leave ghost focus
extendSelectionToends editing; combobox no-op re-select skips undo- Pin + resize no longer clips the first data column
- Portaled editors (
data-grid-cell-editor) commit on mouse pick - Persistence demo rejects incomplete creates (Name + Email) with
failedRowIdshighlight
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”- 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