Skip to content

Changelog

Latest updates and release notes for Niko Table.

  • Base UI support — all 31 registry items install cleanly into both shadcn generations: Radix (new-york) and Base UI (base-nova, the current shadcn init default). 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/deleted changeset with reconcile. 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
  • normalizeFiltersFromUrl exported from filters/table-filter-menu (counterpart of serializeFiltersForUrl) 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 validationCellState.error tooltip 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.flex overrides which column fills; meta.flex: false / TableMeta.disableFlexFill opt 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 its columnSizing + onColumnSizingChange into <DataTableRoot> and resized widths persist to localStorage (immediate write, cross-tab sync). Persist Widths
  • Docs — full registry catalog (resize + Data Grid + axis DnD packages), Install Everything lists all items, homepage/CliCommandCode namespace 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
  • Server-side examples reworked around a database-agnostic wire contract — one serializable query, a fenced MOCK SERVER section 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 with space-y-2
  • Installation notes both shadcn generations are supported (new-york Radix and base-nova Base UI)
  • Docs Source/API links use Astro DocsLink (aligned outline buttons; Starlight MDX stripped React Button 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-practices skill teaches Data Grid composition
  • Import DnD from data-table-row-dnd-structure / data-table-column-dnd-structure (and virtualized twins)
  • DEFAULT_MIN_COLUMN_SIZE moved to lib/constants (root no longer imports resize-handle for it)
  • enableSorting defaults 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
  • data-table-dnd-structure.tsx / data-table-virtualized-dnd-structure.tsx (no re-export shims — use the axis files)
  • Fresh installs were brokenlib/row-click.ts and lib/create-scroll-handler.ts were imported by core files but shipped by no registry item; both now ship with data-table
  • Server-side examples: single-date filters (ms timestamps) match server-side; large page sizes scroll inside a maxHeight container 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
  • useColumnSizingPersistence skips redundant mount re-reads and rejects non-positive stored widths
  • resolveColumnWidth keeps a flex column’s declared size when resizing is off (checks resizing before isFlex)
  • 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 minSize 40px when resize is enabled
  • DataTableDndBody supports column resize (Row DnD + <DataTableColumnResize />)
  • DnD contexts use React.useId() to avoid SSR aria-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 (columns in columnLayoutSignature) — dynamic columns no longer leave body cells / borders stale
  • Coredata-row-index is display order (flashRows / scroll after sort/filter)
  • Core — empty/loading colSpan uses visible columns; resize width cleanup restores prior table styles
  • CoredaysAgo() 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…); clearAll keeps seed row count
  • Scroll-into-view with right-pinned columns
  • Undo/redo / filter / hide no longer leave ghost focus
  • extendSelectionTo ends 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 failedRowIds highlight
  • getRowMemoKey prop — all six body components (DataTableBody, DataTableVirtualizedBody, DataTableDndBody, DataTableDndColumnBody, DataTableVirtualizedDndBody, DataTableVirtualizedDndColumnBody) now accept getRowMemoKey?: (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 column cell definitions become stale. See the Inline Edit Table example.
  • DataTableVirtualizedFlexHeader — flex-layout header for DataTableVirtualizedDndBody. Pick by body: plain → DataTableVirtualizedHeader, row-DnD → DataTableVirtualizedFlexHeader, column-DnD → DataTableVirtualizedDndHeader.
  • Options with count: 0 are 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 passing count: undefined.
  • autoResetPageIndex defaults to false (was true). Sort/filter changes now preserve the pagination cursor. Opt back in via config={{ autoResetPageIndex: true }}.
  • TableRangeFilter[min, max] memo depends on faceted scalars, so the range refreshes on data change. formatValue no longer locale-formats numbers (type="number" inputs reject localized output).
  • TableSliderFilter clear button — always stopPropagation() (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 effectrowMemoKey is now in the useEffect dependency 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 from table.getState().sorting instead of the closure-captured sorting.
  • 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 /> mutate meta.options to 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 and meta.options is 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.
  • TableColumnFacetedFilterMenulimitToFilteredRows decoupled from multiple default for caller options.
  • Cross-table “state update on unmounted component” warning silenced via mount-ref guards on every default state setter.
  • onRowClick event type unified to React.MouseEvent<HTMLElement> across all bodies.
  • Virtualized DnD body — expanded-row height re-measured on toggle without losing useSortable registration.
  • Virtualized non-DnD body — setColumnsLocked race fixed; columnsLocked gates measureElement.
  • Virtualized bodies — data-index uses virtualRow.index; click delegation via stable data-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 — onRowClick widened to React.MouseEvent<HTMLElement>.
  • Column-DnD bodies (virtualized + non-virtualized) — expanded-row rendering matched to other bodies.
  • VirtualizedDraggableRow — added data-index alongside data-row-index.
  • FILTER_OPERATORS.RELATIVE hidden from the date-filter dropdown (kept in catalogue for server-side consumers); per-row console.error throttled.
  • getObjectHash — full sorted-keys join (was first-3 keys, false-negatived row selection with sequential IDs).
  • Scroll handler rAF-coalesced; onScrolledTop / onScrolledBottom fire only on the leading edge (false→true).
  • Row-click guard hardened — suppresses clicks during active text selection; recognizes textarea, select, label, [contenteditable], and ARIA combobox/menuitem/textbox.
  • Slider filters — facetedMin/facetedMax hoisted into memo deps so [min, max] stays reactive on data change.
  • TableViewMenu visible-columns memo keys on table.options.columns (was just table ref).
  • DataTableBody — row clicks delegated to <tbody>, removing one listener per row.
  • tableOptions — 6 inline fallback setters extracted to stable useCallbacks; deps trimmed to destructured state / initialState / globalFilterFn.
  • expandColumnId memoized in all six bodies (was O(rows × cols) per render).
  • DataTableColumnHeaderRoot context value memoized.
  • useKeyboardShortcuts listener no longer re-attaches every render.
  • onRowSelection no longer fires on initial mount — user-driven changes only.
  • handleRowSelectionChange honors full Updater<T> contract; side effects moved to useEffect.
  • DataTableLoading self-gates on isLoading.
  • useGeneratedOptions — eliminated double getFilteredRowsExcludingColumn walk.
  • Row-click guard moved to lib/row-click.ts (was inlined in 6 places); body-scroll listener extracted to lib/create-scroll-handler.ts.
  • DnD virtualized bodies don’t gate measureElement on columnsLocked — flex layout has no auto-layout pass.
  • Virtualized table — column spacing no longer compresses on wide datasets. The column-lock effect now sets tableEl.style.minWidth to the sum of visible column.getSize() values before measuring, so the table can expand beyond its scroll container instead of being squeezed by w-full.
  • DataTableLoadingMore and DataTableVirtualizedLoadingMore — composable “loading more” rows for infinite-scroll tables. Self-gate on isFetching.
  • onNearEnd + prefetchThreshold on DataTableVirtualizedBody — virtualizer-index-driven prefetch trigger for infinite scroll. Strictly better than onScrolledBottom: catches fast scrolls, scrollbar drag, scrollToIndex() jumps, and short initial renders.
  • Virtualized table — switched to native <table> layout with measure-and-lock column sizing. The browser distributes column widths from content; DataTableVirtualizedBody measures each <th> after first data render and locks to table-layout: fixed.
  • Scroll listeneronScrolledBottom / onScrolledTop now wire up even when onScroll isn’t provided (was silently dead in all four bodies).
  • Faceted filter — dynamicCounts honored on every code path (was silently ignored in fallback useMemo and the auto-generated branch).
  • Faceted filter — caller-supplied options go through count enrichment instead of short-circuiting.
  • Faceted filter — TableColumnFacetedFilterMenu forwards dynamicCounts to the underlying hook.
  • Faceted filter — zero-count options no longer disappear.
  • Faceted filter — multi-select defaults limitToFilteredRows to false (single-select keeps true). Expressed as limitToFilteredRows ??= !multiple.
  • preserve merge strategy — returns user-defined meta.options untouched. Only augment injects 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, and pnpm build on every PR and push to main.
  • New example pages: Infinite Scroll Table (non-virtualized) and Infinite Scroll Virtualized Table.
  • Core overview reference adds DataTableLoadingMore, DataTableVirtualizedLoadingMore, onNearEnd, prefetchThreshold.

  • Row click delegated at <tbody> instead of per-row across all six body components.
  • DataTableRoot, TablePagination, DataTableAside — handlers memoized with useCallback for stable references.
  • TablePagination — page input uses local draft state. Page index commits on blur or Enter.
  • TablePaginationonPageSizeChange now 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: 0 so they stick vertically when the header is sticky.
  • DataTableRoot — docs list the required children prop and the optional state prop.

The first public release of Niko Table — a composable, shadcn-compatible data table component registry built with TanStack Table and React.

  • 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)
  • 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
  • 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