Data Table
A sortable, searchable, filterable data table for TanStack Router with cursor pagination and URL state persistence.
ContentDataTable is the core data table component powering VitNode AdminCP lists. Every user interaction - page navigation, column sorting, search queries, and filters - writes directly to the URL query string, making table state fully bookmarkable and shareable.
Preview
| Name | Role | Status | ||
|---|---|---|---|---|
| John Doe | jon_doe@mail.com | Admin | Active | |
| Jane Smith | jane_smith@mail.com | Editor | Inactive | |
| Alice Johnson | alice_johnson@mail.com | Viewer | Active | |
| Bob Brown | bob_brown@mail.com | Admin | Inactive |
Quick start
Render the table inside a DataTableNavigationProvider:
import type { ColumnDef } from '@vitnode/core/components/table/data-table-content'
import { ContentDataTable } from '@vitnode/core/components/table/content'
import { DataTableNavigationProvider } from '@vitnode/core/components/table/provider'
interface Member {
id: number
name: string
email: string
role: string
}
const columns: ColumnDef<Member>[] = [
{ accessorKey: 'name', header: 'Name', enableSorting: true },
{ accessorKey: 'email', header: 'Email' },
{ accessorKey: 'role', header: 'Role' },
]
interface MembersSearch {
cursor?: string
}
interface MembersTableProps {
data: Member[]
navigate: (options: { search: MembersSearch }) => Promise<void>
search: MembersSearch
}
export const MembersTable = ({ data, navigate, search }: MembersTableProps) => {
return (
<DataTableNavigationProvider navigate={navigate} search={search}>
<ContentDataTable
id="members-table"
columns={columns}
data={data}
order={{
defaultOrder: { column: 'name', order: 'asc' },
}}
search={{ placeholder: 'Search members...' }}
/>
</DataTableNavigationProvider>
)
}Plugin Route Integration
Give an AdminCP route to the plugin, then pass its typed search and navigate
props into the table. The table remains reusable and the host stays out of it.
import type { PluginRoutePageProps } from '@vitnode/core/routing'
import { definePluginRoute } from '@vitnode/core/routing'
import { MembersTable } from '../views/members-table'
interface MembersSearch {
cursor?: string
}
export const route = definePluginRoute({
parseSearch: (input) => {
const search = input as Record<string, unknown>
return {
cursor: typeof search.cursor === 'string' ? search.cursor : undefined,
}
},
load: async ({ search }) => await fetchMembers(search),
})
const AdminMembersPage = ({
loaderData,
navigate,
search,
}: PluginRoutePageProps<Member[], MembersSearch>) => (
<MembersTable data={loaderData} navigate={navigate} search={search} />
)
export default AdminMembersPageFilters
Add static or asynchronous dropdown filters to the table toolbar:
<ContentDataTable
id="members-table"
columns={columns}
data={data}
filters={[
{
id: 'role',
title: 'Role',
options: [
{ label: 'Admin', value: 'admin' },
{ label: 'Member', value: 'member' },
],
},
]}
/>Bulk Selection & Actions
Enable row checkboxes and execute bulk operations with useDataTableSelection:
import { useDataTableSelection } from '@vitnode/core/components/table/hooks/use-data-table-selection'
export const BulkActionsToolbar = () => {
const { selectedRows, resetSelection } = useDataTableSelection()
if (!selectedRows.length) return null
return (
<div className="flex items-center gap-2 p-2 bg-muted rounded-md">
<span>{selectedRows.length} selected</span>
<button onClick={() => deleteSelected(selectedRows)}>Delete</button>
</div>
)
}ContentDataTable Props
Prop
Type