table
Show rows of data with sorting, paging and filters that stay in the URL.
Admin screens, dashboards and reports all need the same thing: a table people can sort, page through and filter. kitr/table keeps those choices in the page's URL, so a filtered view survives a reload, works with the back button, and can be shared as a link.
It is TanStack Table v9, re-exported, plus useUrlTableState and a ready-made feature set.
Install
kitr add tableFirst time? Run npm i -g kitr once. Inside a project, the global kitr runs your project's own version.
This installs @kitr/table and @tanstack/react-table. There is no config file. kitr ui add data-table copies a styled <DataTable> into app/components/ui.
Sorting and paging in the URL
Three steps: let the route accept table search params, bind the table to them, and fetch the matching page.
// app/routes/projects/index.tsx
import { createFileRoute } from '@tanstack/react-router'
import { useSuspenseQuery } from 'kitr/query'
import { parseTableSearch, useUrlTableState } from 'kitr/table'
import { DataTable } from '#app/components/ui/data-table'
import { columns } from '#app/features/projects/columns'
import { projectsQuery } from '#app/features/projects/queries'
export const Route = createFileRoute('/projects/')({
// 1. ?page=2&size=50&sort=-createdAt&filter[status]=active
validateSearch: parseTableSearch,
loaderDeps: ({ search }) => search,
loader: ({ context, deps }) => context.queryClient.ensureQueryData(projectsQuery.options(deps)),
component: Projects,
})
function Projects() {
const search = Route.useSearch()
const navigate = Route.useNavigate()
const { data } = useSuspenseQuery(projectsQuery.options(search))
// 2. Sorting, paging and filters read from and write to the URL.
const urlState = useUrlTableState({
search,
setSearch: (patch) => navigate({ search: (s) => ({ ...s, ...patch }) }),
})
// 3. The table itself.
return <DataTable columns={columns} data={data.rows} rowCount={data.total} {...urlState} />
}
Without the registry component, pass the same options to useTable with features: dataTableFeatures and render the rows yourself.
Changing the sort resets to page one. Default values (page 1, the default page size, no sort) are left out of the URL to keep links short.
How the URL looks
| Table state | URL | Notes |
|---|---|---|
| Page 3 | ?page=3 |
One-based, unlike TanStack's zero-based pageIndex. |
| 50 rows per page | ?size=50 |
Up to 500. |
| Name A to Z, then newest | ?sort=name,-createdAt |
A leading - means descending. |
| Status is active | ?filter[status]=active |
One entry per filtered column. |
parseTableSearch validates all of this and drops anything malformed, so a hand-edited URL can't break the page.
Server-side by default
useUrlTableState sets manualSorting, manualPagination and manualFiltering, because the usual case is a server that returns one page at a time. Pass the same search object to your query so the API sorts and filters.
For a small dataset you sort in the browser, override them after the spread: { ...urlState, manualSorting: false, manualPagination: false }.
Next steps
- API reference
- TanStack Table docs: everything there works as written, imported from
kitr/table.