Migrate from TanStack Table
TanStack Table is a superb headless engine
— you keep total control because it renders nothing: no markup, no toolbar, no
filter inputs, no pagination controls, no URL sync. AdaptTable shares that
philosophy. @adapttable/core is also headless (prop-getters, no forced
markup), but it comes with the parts you rebuild on every TanStack project
already wired: a filter UI, a toolbar, pagination, URL-synced state, and saved
views. And when you don’t need bespoke markup, an adapter renders native kit
components so you can delete the table UI entirely.
TanStack is multi-framework (React, Vue, Svelte, Solid, …); AdaptTable is React-only. If you’re not on React, stay on TanStack. This page is for React teams tired of rebuilding the same chrome.
What you gain
Section titled “What you gain”Everything TanStack leaves to you (verified from its docs), done for you:
- A filter UI. TanStack gives you
columnFiltersstate andgetFacetedUniqueValues, but you render every input. AdaptTable derives kit-native widgets, removable chips, and URL params from afilterdeclaration. See filtering. - A toolbar, pagination, and search — none of which exist in TanStack; you
wire the buttons around
nextPage()/setPageIndex()yourself. - URL-synced state. TanStack has no URL API; you serialize
sorting/columnFilters/paginationby hand. AdaptTable does it. See URL state. - Saved views — no concept in TanStack. See saved views.
- No opt-in row models. You don’t import
getSortedRowModel/getFilteredRowModel/getPaginationRowModel— search, filter, sort, and paging run by default on the frontend tier. - Native kit UI, optional. Keep headless control with
useDataTableprop-getters, or adopt an adapter (@adapttable/mantine,mui, …) and drop your hand-written markup.
Install
Section titled “Install”Headless core only, or core plus an adapter for ready UI:
# Headless (keep rendering your own markup, TanStack-style)pnpm add @adapttable/core
# Batteries — native components for your kit (delete the markup)pnpm add @adapttable/core @adapttable/mantine @mantine/core @mantine/hooksConcept mapping
Section titled “Concept mapping”| TanStack Table | AdaptTable | Notes |
|---|---|---|
useReactTable({ data, columns, getCoreRowModel }) | useDataTable({ source, columns }) or an adapter <DataTable> | source from useFrontendData; adapters accept data directly. |
getSortedRowModel / getFilteredRowModel / getPaginationRowModel | (none needed) | Applied automatically on the frontend tier. |
flexRender(cell.column.columnDef.cell, …) | adapter renders, or prop-getters (getCellProps, …) | Headless route mirrors TanStack’s model. |
columnHelper.accessor("name", …) | { key: "name" } | Dot paths work in both. |
columnHelper.accessor(row => …) (accessorFn) | { key, accessor: (row) => … } | — |
columnHelper.display({ cell }) | { key, Cell } | Cell receives { row, rowIndex }. |
header / cell / footer | header / Cell (or accessor) / summaryRow | Footer is one table-level function. |
enableSorting / state.sorting / onSortingChange | sortable per column (+ multiSort) | AdaptTable owns sort state. |
columnFilters + getFilteredRowModel + your inputs | column filter shorthand + filters array | Widgets + chips built for you. |
state.pagination + getPaginationRowModel + your controls | automatic pagination | Paged on desktop, infinite on mobile. |
enableRowSelection + state.rowSelection + your checkboxes | bulkActions / selectedIds / onSelectionChange | — |
getExpandedRowModel + row.getToggleExpandedHandler() | renderRowDetail | Toggle UI is provided, not hand-added. |
columnVisibility / columnOrder / columnPinning state | enableColumnMenu / columnLayout | One built-in menu instead of hand-wired state + DnD. |
manualSorting / manualFiltering / manualPagination | onQueryChange (or source via useBackendData) | One consolidated server query. See data tiers. |
| (serialize state to the URL yourself) | urlSync / urlKey / savedViews | On by default. |
Before / after
Section titled “Before / after”Before — TanStack: opt-in row models, flexRender, and hand-built
pagination:
import { createColumnHelper, flexRender, getCoreRowModel, getPaginationRowModel, getSortedRowModel, useReactTable,} from "@tanstack/react-table";
const col = createColumnHelper<Person>();const columns = [ col.accessor("name", { header: "Name" }), col.accessor("role", { header: "Role" }),];
function PeopleTable({ people }: { people: Person[] }) { const table = useReactTable({ data: people, columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel(), });
return ( <> <table> <thead> {table.getHeaderGroups().map((hg) => ( <tr key={hg.id}> {hg.headers.map((h) => ( <th key={h.id} onClick={h.column.getToggleSortingHandler()}> {flexRender(h.column.columnDef.header, h.getContext())} </th> ))} </tr> ))} </thead> <tbody> {table.getRowModel().rows.map((row) => ( <tr key={row.id}> {row.getVisibleCells().map((cell) => ( <td key={cell.id}> {flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ))} </tr> ))} </tbody> </table> {/* …and you still hand-write the pagination buttons, search box, filter inputs, and URL syncing. */} </> );}After — an adapter renders native UI; search, sort, filters, pagination, and URL state come built in:
import { DataTable } from "@adapttable/mantine"; // or mui, chakra, antd, …
function PeopleTable({ people }: { people: Person[] }) { return ( <DataTable data={people} rowKey={(r) => r.id} columns={[ { key: "name", sortable: true }, { key: "role", filter: { type: "select", options: "auto" } }, ]} /> );}Prefer to keep your own markup? Stay headless with @adapttable/core:
useFrontendData for the source and useDataTable for the prop-getters
(getTableProps, getHeaderCellProps, getSortButtonProps, getRowProps,
getCellProps, getSearchInputProps) — the same headless shape as TanStack,
but with filters, pagination, and URL state already handled.
Gotchas
Section titled “Gotchas”- React only. On Vue/Svelte/Solid/Angular, keep TanStack — AdaptTable has no adapter for those.
rowKeyis required (AdaptTable’sgetRowId).- Delete the opt-in row models. No
getSortedRowModel/getFilteredRowModel/getPaginationRowModelimports — those run for you. - Filters are declarative, not hand-rendered. Swap your custom filter inputs
for a
filtershorthand; a bespoke control can still be JSX infilters. - You keep the headless escape hatch.
useDataTablereturns prop-getters, so moving to AdaptTable doesn’t mean giving up control — it means not rebuilding the chrome first.
Where next
Section titled “Where next”- Concepts — the headless engine and
TableSource. - Getting started · Filtering · Data tiers · API reference.
- Comparison — where each library fits.