No consumer migration is required. Tables using infiniteScroll continue to
hide pagination controls. If they also supply totalRowCount, the package now
renders its native record-count footer; omit totalRowCount or set
showFooter={false} to retain no footer. Applications that previously passed a
custom count-only footer child can remove that child after adopting 5.3.0.
Keep getRowId at module scope or memoize it with React.useCallback; the
README examples now consistently demonstrate that stable identity contract.
No consumer migration is required for 5.2.1. The shadcn adapter now uses
bg-input for data-table search input groups; adjacent toolbar controls remain
on bg-card. No prop, type, entrypoint, or persisted state changed.
No public API was removed. In layoutMode="fill", a configured size is now
treated as a preferred width, matching TanStack Table's sizing model. The last
currently rendered data column that is still able to grow consumes the unused
table width, and the internal __spacer__ is omitted.
If a configured column must remain fixed, give it a growth-locking maxSize.
For a completely fixed column, use the same value for minSize, size, and
maxSize. Controlled or user-resized widths remain fixed for layout purposes.
The spacer is still inserted when every currently rendered data column is
growth-locked.
No consumer migration is required for 5.1.0. The internal __spacer__ column
now uses the same header, body-row, and summary-row borders/backgrounds as its
neighboring cells, so fixed-width fill layouts remain visually continuous
through right-pinned actions.
The spacer remains package-owned, semantically inert, excluded from keyboard
navigation and export, and subject to the same sizing and overflow rules as in
5.0.0. Applications must continue not to define or reference __spacer__.
Version 5.0.0 upgrades from @tanstack/react-table@8.21.3 to v9.1.2. It
targets v9 directly and does not use the deprecated useLegacyTable
compatibility hook.
Install the major release from its Git tag:
pnpm add github:Dastari/data-table-pro#v5.0.0The component-level wrapper remains compatible where its contract is owned by this project:
- Controlled v8-style
Record<string, boolean>row selection is normalized to v9's selected-only state at the integration boundary. - Public and persisted column pinning remains
{ left, right }. Only the TanStack instance receives{ start, end }, so existing preferences, URLs, saved views, labels, and physical sticky-column styling remain valid. apiRef.current.getState()remains the stable wrapper snapshot API. Code that asks for the underlying v9 table throughapiRef.current.getTable()reads its current TanStack state fromtable.store.state.- Existing package entrypoints, adapters, persisted preferences, saved views, and URL-state formats are unchanged.
The implementation itself now uses useTable, one explicit feature registry,
v9 row-model slots, and project-owned table/row/column/cell/header type aliases
that contain TanStack's new feature generic.
Direct TanStack-facing code must adopt these v9 changes:
| V8 form | V9 form used by the project |
|---|---|
sortingFn |
sortFn |
callable custom AggregationFn |
context-based DataTableAggregationFn definition |
table.getState() |
table.store.state (snapshot) |
getPrePaginationRowModel() |
getPrePaginatedRowModel() |
internal pinning left / right state and methods |
logical start / end state and method families |
| destructured/spread row, cell, column, or header methods | call methods through their owning instance |
getIsSome*Selected() means some but not all |
means at least one; combine with !getIsAll*Selected() for indeterminate state |
raw Table<TData>, Row<TData>, ColumnDef<TData>, etc. |
add v9's leading TFeatures generic or use project-owned DataTable* types |
| primitive row data | record or array row data |
For column sorting, rename only the TanStack column-definition option:
const columns: Array<DataTableColumnDef<Person>> = [
{
accessorKey: "name",
sortFn: "alphanumeric",
},
];Register a custom aggregation as a definition with an aggregate method:
<DataTable
aggregationFns={{
doubledSum: {
aggregate: ({ getValue, rows }) =>
rows.reduce((total, row) => total + Number(getValue(row)), 0) * 2,
},
}}
columns={[
{ accessorKey: "team", enableGrouping: true },
{ accessorKey: "hours", aggregationFn: "doubledSum" },
]}
data={rows}
getRowId={(row) => row.id}
/>The wrapper and underlying table now expose deliberately different pinning and state boundaries:
const wrapperState = apiRef.current?.getState();
// wrapperState.columnPinning is still { left, right }
const table = apiRef.current?.getTable();
const tanStackState = table?.store.state;
// tanStackState.columnPinning is { start, end }Do not extract prototype methods from rows, cells, columns, or headers:
// Incorrect in v9: const { getValue } = row;
const value = row.getValue("name");
const values = rows.map((currentRow) => currentRow.getValue("name"));For an indeterminate selection checkbox, test both v9 predicates:
const indeterminate =
table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected();Consumers that construct their own TanStack tables alongside this package, or
that rely heavily on the instance returned by apiRef.current.getTable(),
should also apply the remaining v9 architecture changes:
- replace
useReactTable(options)withuseTable({ ...options, features }); - register each used feature with
tableFeatures(); the core row model is automatic, while optional row models move fromget*RowModeltable options tocreate*RowModel()feature slots; - move
sortingFns,filterFns, andaggregationFnsregistries into the feature registry and renamesortingFnstosortFns; - split table-level
enablePinningintoenableColumnPinningandenableRowPinningwhere applicable; - register
columnSizingFeatureandcolumnResizingFeatureseparately, and renamecolumnSizingInfo,setColumnSizingInfo, andonColumnSizingInfoChangeto theircolumnResizingequivalents; and - replace underscore-prefixed v8 internals with public v9 methods, including
row.getAllCellsByColumnId(),table.getTopRows(),table.getCenterRows(), andtable.getBottomRows().
The package's own integration has completed this checklist. These items apply only to consumer code that directly constructs or operates on TanStack instances.
These are optimizations and additional coverage, not release blockers or unrecorded compatibility requirements:
- replace the broad built-in filter/sort/aggregation registries with an audited tree-shakeable registry while retaining documented string names;
- add focused consumer fixtures for v9-only
sortFn, custom filters, and custom aggregations; - evaluate narrower
useTableselectors andSubscribeboundaries after measuring render behavior.
Version 4.5.0 removes no public prop, type, or package entrypoint. Existing
tables retain hover-only scrollbars by default; set
scrollbarVisibility="always" on data tables that require persistent
horizontal and vertical scrollbars.
Fixed-width layoutMode="fill" tables no longer stretch their last visible
data column. When all currently visible data columns are fixed, the table
automatically creates an empty flexible spacer after the data columns and
before right-pinned row actions. The spacer uses the same header and row
borders and backgrounds as adjacent cells so the table grid remains visually
continuous. Applications should not define, order, pin, export, or otherwise
reference the reserved __spacer__ column.
The spacer decision follows current columnVisibility, including leaves in
grouped definitions and responsive visibility. Restoring a genuine flexible
data column removes the spacer. If fixed column minimums exceed the viewport,
the table keeps their widths and scrolls horizontally rather than compressing
them. These rules are the same for standard and virtual table adapters.
Version 4.4.0 removes no public prop, type, or package entrypoint. A 4.3 table keeps native table semantics and its existing toolbar, paging, editing, and server behavior until a new option is enabled.
- Use
enableGrouping,grouping/onGroupingChange, orinitialState.groupingto adopt grouping.DataTableState.groupingremains optional so existing complete state object literals compile unchanged. - Use
interactiveGrid(oraccessibility={{ mode: "grid" }}) only for an application-style keyboard grid. AddenableCellSelectionseparately when rectangular range selection is wanted; ordinary row selection is independent. - Use
clipboardfor opt-in copy/paste behavior.copyToClipboard()remains asynchronous and now loads its implementation on first use. Settingclipboard={{ copy: false }}continues to disable imperative/default range copy. - Use
toolbarDataOperationsfor the searchable column manager, filter chips, reset, and saved-view UI. ExistingsavedViewsAPI-ref commands still work without built-in controls. Newly created saved views include grouping by default; an explicitsavedViews.sliceslist remains authoritative. - Use
autoPageSize,stateOverlay,enablePrint, andenableFullscreenindependently. Their optional implementations load only when requested. - Data-source callbacks may consume the new
globalFilter,grouping,aggregations, andexpansionPathrequest fields and returnrowIds,facets,aggregates, andmetadata. Existing sources can ignore all new fields and keep returning their prior result shape. - Numeric range filters without explicit
min/maxnow expose bounds from TanStack's local faceted row model. Set explicit limits to retain fixed application-defined bounds; manual/server filters remain server-owned.
The base gzip budget rises from 42 KiB to 48 KiB with a measured 46.1 KiB static graph. Clipboard, enhanced toolbar operations, auto sizing, error overlays, virtualization, and the data-source hook are not added to that default static path when unused.
Column virtualization is not part of 4.4. The package keeps native table layout and grouped-header, pinning, resizing, detail-row, and accessibility correctness instead of shipping a partial body-only virtualizer.
Version 4.3.0 removes no prop, type, or entrypoint. Existing flat-row tables,
detail panels based on renderExpandedRow, manual server data, and column
reordering continue to work.
- New server integrations can import
useDataTableDataSourcefromdata-table-pro/data-source; existingdataplus manual flags remain supported. renderExpandedRowremains a deprecated detail-panel bridge. UsedetailPanel={{ render, getRowCanExpand }}when adoptinggetSubRowsso application details and hierarchical expansion have independent state.- Nested column groups are now locked during reordering by default. Add
freeReordering: trueto every group boundary that intentionally permits a leaf to cross it. - Row pinning is opt-in through
enableRowPinning. AddrowPinningto custom persistence/saved-view slice lists if those lists override the defaults. - Responsive presentation is owned by
data-table-pro/styles.csscontainer queries. Consumers should not add viewport media-query copies of the table breakpoints.
No existing import or prop needs to change. The base adapter entrypoints still
accept virtualization, but TanStack Virtual is now loaded in an on-demand
chunk only when table or card virtualization is enabled:
// Still supported; virtual code loads on first use.
import { DataTable } from "data-table-pro";
<DataTable virtualization columns={columns} data={rows} getRowId={getRowId} />;Applications that always virtualize can remove that first-use async boundary by changing only the import:
import { DataTable } from "data-table-pro/virtual";Equivalent eager entrypoints are data-table-pro/heroui/virtual and
data-table-pro/thegridcn/virtual. Props, types, styling, state, and adapter
behavior are unchanged. During on-demand loading, the base entry keeps table
state in the parent component and renders a bounded initial fallback (20 rows
or 12 cards by default). Use fallbackRowCount or fallbackCardCount when a
different first-paint cap is appropriate.
Adapter authors can move from the broad compatibility surface to:
import { createDataTable } from "data-table-pro/adapter";
import { createVirtualDataTable } from "data-table-pro/adapter/virtual";data-table-pro/advanced remains available throughout 4.x. Package JavaScript
is now minified with source maps; generated chunk filenames remain private and
must not be imported directly.
The package peer minimums have moved to:
react@^19.2.8react-dom@^19.2.8nuqs@^2.9.2when usingdata-table-pro/url-state@heroui/styles@^3.2.2when usingdata-table-pro/heroui
No table prop, type, or entrypoint was removed, but applications pinned below one of these peer minimums must update that dependency before consuming the next release.
Repository contributors now need a jsdom 30-supported Node.js release
(^22.22.2, ^24.15.0, or >=26) and pnpm 11.21.0. CI uses Node.js
22.22.2. TypeScript 7.0 removes baseUrl and does not expose the programmatic
compiler API needed by typescript-eslint and tsup. The repository therefore
follows the TypeScript team's side-by-side migration:
@typescript/nativealiases TypeScript 7 and providestsctypescriptaliases@typescript/typescript6for API-based tooling- path mappings use explicit relative paths instead of
baseUrl
Keep both compiler packages until the linting and declaration-bundling tools support the TypeScript 7 API. pnpm 11 also enforces an explicit dependency build policy: esbuild's installer is allowed, while MSW's unused transitive postinstall is denied.
Clickable table rows now preserve their native row semantics instead of
rendering role="button" on a <tr> that may contain checkboxes, links, and
row-action buttons. The row remains focusable and continues to invoke
onRowClick with pointer input, Enter, or Space.
This does not remove or change a public TypeScript API. It is a DOM-semantic
compatibility change: applications or tests that queried
tr[role="button"] must stop relying on that undocumented selector. Prefer a
class returned by getRowClassName, the row containing known cell content, or
an application-owned wrapper/test identifier.
Version 3.0.9 is source-compatible with 3.0.8: no prop, type, or entrypoint was removed. It intentionally corrects three runtime behaviors:
- Client toolbar queries now run through TanStack Table global filtering.
A supplied
globalFilterFnis therefore invoked. KeepenableToolbarQueryFiltering={false}for a display-only query, ormanualFilteringwhen the server owns filtering. - CSV export now defaults to CRLF row endings and neutralizes string values
beginning with
=,+,-, or@. UselineEnding: "\n"only for an LF-specific consumer. UseescapeFormulaValues: falseonly for trusted data when exact legacy output is required. - Manual pagination no longer treats the loaded page length as the total when
both
totalRowCountandpageCountare absent. SupplyhasNextPagefor cursor/unknown-total navigation, or supply a known total as before.
New onActionError handling is additive. Built-in async callbacks no longer
leave rejected promises unhandled; consumers can use the error context to show
their preferred toast, retry, or inline error state.
columnPrefsKey remains functional. It now reads legacy raw preference objects
and upgrades them to a validated, versioned envelope on the next write.
Applications that need schema changes, custom storage, or explicit persisted slices can move additively:
// Before and still supported
<DataTable columnPrefsKey="people" />
// Compatibility replacement
<DataTable
persistence={{
key: "people",
version: 2,
slices: ["visibility", "sizing", "order", "pinning", "density"],
migrate: (payload, targetVersion) =>
migrateTablePreferences(payload, targetVersion),
}}
/>When both props are supplied, persistence takes precedence. No removal is
planned during 4.x.
The split controlled props remain supported. initialState, state, and
onStateChange can be adopted one slice at a time:
const [tableState, setTableState] = useState<DataTableState>(initialState);
<DataTable
state={tableState}
onStateChange={setTableState}
columns={columns}
data={rows}
getRowId={(row) => row.id}
/>During 4.x, a legacy controlled prop takes precedence over its matching unified
slice. For example, pageIndex/pageSize override state.pagination, and
sorting overrides state.sorting. Development builds warn when both are
present so migrations can remove conflicts deliberately.
Column sizing can now be controlled with columnSizing and
onColumnSizingChange. apiRef provides snapshot/restore and reset commands
without requiring imports from data-table-pro/advanced.
Existing query, page, page-size, sort, view, and hidden-row URL behavior is unchanged. New slices are opt-in and versioned:
const url = useDataTableUrlState({
keyPrefix: "people-",
version: 2,
enabled: [
"columnFilters",
"columnVisibility",
"density",
"columnOrder",
"columnPinning",
],
migrate: (payload, targetVersion) =>
migratePeopleUrlState(payload, targetVersion),
});A mismatched enhanced URL schema is ignored unless migrate returns valid
replacement state. URLs created before enhanced slices existed continue to
decode their original fields. Selection requires the explicit
"rowSelection" opt-in so adopting the expanded hook does not place row IDs
in shareable URLs accidentally.
Saved views are additive and do not add built-in controls:
const apiRef = useRef<DataTableApi<Person>>(null);
<DataTable
apiRef={apiRef}
savedViews={{
key: "people",
version: 1,
onChange: setSavedViews,
}}
/>;
const view = apiRef.current?.createSavedView("Operations");
apiRef.current?.applySavedView(view?.id ?? "");
apiRef.current?.renameSavedView(view?.id ?? "", "Ops");
apiRef.current?.deleteSavedView(view?.id ?? "");The default saved-view slices exclude pagination, selection, and expansion.
Opt into them with savedViews.slices only when those transient values are
meaningful to the application.
Use clearPersistedState() to remove preferences without changing live state.
Use resetColumnLayout({ clearPersistence: true }) or
resetState({ clearPersistence: true }) to discard the old payload and restore
initial/default values in one command.
Version 5.0.0 uses its major boundary for the TanStack v9 migration above. It does not remove the unrelated wrapper compatibility APIs previously proposed for 5.0. Those candidates remain deferred until a later major and still require a stable replacement and deprecation period:
| Current compatibility API | Possible future API | Compatibility path |
|---|---|---|
toolbarQueryValue, onToolbarQueryValueChange, toolbarQueryDebounceMs |
globalFilter, onGlobalFilterChange, globalFilterDebounceMs |
Both names must work during a documented deprecation window before removal. |
Split pageIndex/pageSize props and callbacks |
Unified pagination state and onPaginationChange |
Unified state remains additive until the split props are formally deprecated. |
renderExpandedRow and getRowCanExpand for detail content |
detailPanel={{ render, getCanExpand }} |
The explicit detail-panel API must remain stable before tree expansion takes exclusive ownership. |
columnPrefsKey |
Versioned persistence configuration |
columnPrefsKey remains a compatibility shorthand until a later deprecation. |
virtualization on the base component |
Dedicated virtual adapter entrypoints | Both entry styles continue to coexist; base imports load virtual panels on demand. |
Broad data-table-pro/advanced imports |
Stable data-table-pro/adapter contracts |
A complete advanced-import mapping is required before removal. |
Any future removal is gated on:
- at least one stable release containing every replacement
- development warnings for conflicting old/new props
- a codemod for renamed props and entrypoints
- migration fixtures for every adapter, server pagination, URL state, virtualization, detail panels, and persisted state
- safe migration or invalidation of versioned persisted/URL payloads
See the modernization roadmap for sequencing, acceptance criteria, and the complete breaking-change register.
data-table-pro now supports multiple UI adapter entrypoints without changing the table API.
Choose the migration path that matches the UI stack of the host app.
Projects upgrading to 3.0.0 should review these changes:
- The package is ESM-only. Remove
require("data-table-pro")usage. - React peers are now
react@^19.2.0andreact-dom@^19.2.0. nuqsis an optional peer required only when importingdata-table-pro/url-state.- Toolbar search filters local rows by default. Use
manualFilteringfor server-side tables orenableToolbarQueryFiltering={false}when the toolbar input should be display-only. column.meta.filterrenders built-in toolbar filters for text, select, multi-select, boolean, numeric-range, and date-range controls. Range filter state uses serializable{ from, to }objects with inclusive bounds.- New optional APIs cover row expansion, column ordering/pinning, CSV export, density, labels, summary rows, RTL direction, and column preference persistence.
Projects upgrading to 2.0.1 must make these API changes:
- Move
useDataTableUrlStateimports todata-table-pro/url-state. - Rename
searchValuetotoolbarQueryValue. - Rename
onSearchValueChangetoonToolbarQueryValueChange. - Rename
searchPlaceholdertotoolbarQueryPlaceholder. - Rename
searchDebounceMstotoolbarQueryDebounceMs. - Update custom empty-state render functions to read
toolbarQueryValue.
No import-path change is required.
Before:
import { DataTable } from "data-table-pro";After:
import { DataTable } from "data-table-pro";
import "data-table-pro/styles.css";Host app notes:
- keep the existing shadcn-compatible theme tokens
- import
data-table-pro/styles.css - remove copied
.data-table-container-queryand.dt-hide-on-*helpers if they were manually duplicated in app globals
Change imports:
import { DataTable } from "data-table-pro/heroui";Host stylesheet:
@import "tailwindcss";
@import "@heroui/styles";
@import "data-table-pro/styles.css";Host app requirements:
- React 19
- Tailwind CSS v4
- HeroUI style import in the app
@heroui/stylesinstalled in the host app
Migration notes:
- remove assumptions that the table inherits shadcn-specific theme tokens
- HeroUI table internals use HeroUI-compatible slot classes, so shadcn variables such as
--border,--card,--input, and--mutedare not required - keep the
DataTableprops unchanged - downstream app code should only change the import path and host style setup
Change imports:
import { DataTable } from "data-table-pro/thegridcn";Host stylesheet:
@import "tailwindcss";
@import "data-table-pro/styles.css";
@import "./thegridcn-theme.css";Host app requirements:
- a host-supplied The Gridcn theme or token stylesheet
- Tailwind CSS v4 recommended
Migration notes:
- no
shadcn addstep is required fordata-table-proitself - no 3D or showcase-only The Gridcn setup is required
- keep the
DataTableprops unchanged
Before:
import { useDataTableUrlState } from "data-table-pro";After:
import { useDataTableUrlState } from "data-table-pro/url-state";The same change applies if the hook was previously imported from data-table-pro/heroui or data-table-pro/thegridcn.
Before:
<DataTable
searchValue={query}
onSearchValueChange={setQuery}
searchPlaceholder="Search people"
searchDebounceMs={150}
/>After:
<DataTable
toolbarQueryValue={query}
onToolbarQueryValueChange={setQuery}
toolbarQueryPlaceholder="Search people"
toolbarQueryDebounceMs={150}
/>Before:
emptyState={({ searchValue }) => <div>No matches for {searchValue}</div>}After:
emptyState={({ toolbarQueryValue }) => (
<div>No matches for {toolbarQueryValue}</div>
)}DataTableimport paths- adapter entrypoints
- public exported types through
data-table-pro/types - layout and styling integration requirements