Data Table
Data Table
A typed table with sorting, search, filters and pagination that keeps its state in the URL and reads its rows from the VitNode API.
Preview
Usage
import type { ColumnDef } from '@vitnode/core/components/table/data-table-content'
import { ContentDataTable } from '@vitnode/core/components/table/content'const columns: ColumnDef<User>[] = [
{ accessorKey: 'name', header: 'Name' },
{ accessorKey: 'email', header: 'Email' },
{ accessorKey: 'status', header: 'Status', align: 'center' },
{
id: 'actions',
header: '',
align: 'right',
cell: ({ row }) => <EditUserButton id={row.id} />,
},
]
<ContentDataTable
id="users-table"
columns={columns}
edges={data.edges}
pageInfo={data.pageInfo}
order={{ defaultOrder: { column: 'name', order: 'asc' } }}
/>Every row needs a numeric id. Render the table inside a
DataTableNavigationProvider, or its controls throw.
Connect it to the URL
The table never talks to the router. Search, filters, sorting and the pager all
hand a new query string to navigate, and the table renders whatever
searchParams says.
import type { DataTableNavigation } from '@vitnode/core/components/table/navigation'
import { DataTableNavigationProvider } from '@vitnode/core/components/table/navigation'
const navigation = React.useMemo<DataTableNavigation>(
() => ({
searchParams: new URLSearchParams(search),
navigate: async nextSearch => {
await navigate({
resetScroll: false,
search: Object.fromEntries(new URLSearchParams(nextSearch)),
})
},
}),
[navigate, search],
)
<DataTableNavigationProvider value={navigation}>
<ContentDataTable {...props} />
</DataTableNavigationProvider>The route's loader then fetches the page those params describe:
const res = await fetcher({
plugin: '@vitnode/core',
method: 'get',
module: 'admin/users',
path: '/list',
args: { query: search },
withPagination: true,
})Columns
An accessor column binds to a field with accessorKey, which only accepts
keyof T, so typos fail at compile time. A display column (actions,
badges) has no field and needs a string id.
cell replaces the default text and gets { row, allData }. align sets
'left' (default), 'center' or 'right' for the header and cells. Right
suits numbers and actions.
Sorting
List sortable columns in order.columns. Clicking a header flips between
ascending and descending and writes ?orderBy=&order= to the URL.
defaultOrder applies when the URL says nothing.
order={{
columns: ['name', 'createdAt'],
defaultOrder: { column: 'createdAt', order: 'desc' },
}}Clickable rows
rowOpens makes the whole row a target. It's focusable, and Enter or Space
opens it too. Clicks on links, buttons and inputs inside the row are ignored.
rowOpens={user => navigate({ to: `/admin/core/users/${user.id}` })}Features
Search
A debounced search box that writes to the URL.
Filters
Faceted multi-select filters, static or async.
Pagination
Numbered pages and a rows-per-page select.
Bulk actions
Tick rows and act on all of them at once.
Expandable rows
A detail panel under each row.
Draggable rows
Reorder rows and save the order.
Custom rows
Render each row as your own layout instead of cells.
Grouped rows
Sort rows into categories, each under its own heading.
API
Prop
Type
Other props go to the underlying Table.