UI components
DataTable
Reusable, type-safe data table with client/server modes.
AI Skill for data-tables
Prompt: Type
/data-tables in your Copilot / Cursor or other chat to use skill with the provided context./data-tables Build this table using the shared DataTable module. Requirements: - Data model: [model name] - Columns: [list columns] - Toolbar filters (SmartFilterBar): [search/status/date/etc] - Server query mapping: [api input shape]
Overview

The shared module lives at src/shared/components/data-table and supports both client and server modes.
import {
DataTable,
DataTableColumnHeader,
useDataTable,
type BatchActionComponentProps,
type DataTableApiParams,
type DataTableQueryState,
} from '@/shared/components/data-table';Core table state:
sortingcolumnFilterspaginationrowSelection
Minimal Working Example
'use client';
import { useMemo } from 'react';
import type { ColumnDef } from '@tanstack/react-table';
import { parseAsString, useQueryStates } from 'nuqs';
import { DataTable } from '@/shared/components/data-table';
import {
type SmartFilterDefinition,
SmartFilterBar,
} from '@/shared/components/filters';
type UserRow = {
id: string;
name: string;
email: string;
status: 'active' | 'invited';
};
const rows: UserRow[] = [
{ id: '1', name: 'Ada Lovelace', email: '[email protected]', status: 'active' },
{ id: '2', name: 'Grace Hopper', email: '[email protected]', status: 'active' },
{ id: '3', name: 'Linus Torvalds', email: '[email protected]', status: 'invited' },
{ id: '4', name: 'Margaret Hamilton', email: '[email protected]', status: 'active' },
];
export function ExampleUsersTable() {
const [queryState, setQueryState] = useQueryStates({
search: parseAsString.withDefault(''),
status: parseAsString.withDefault(''),
});
const columns = useMemo<ColumnDef<UserRow>[]>(
() => [
{ accessorKey: 'name', header: 'Name' },
{ accessorKey: 'email', header: 'Email' },
{ accessorKey: 'status', header: 'Status' },
],
[],
);
const smartFilters = useMemo<SmartFilterDefinition[]>(
() => [
{
key: 'search',
label: 'Search',
type: 'input',
value: queryState.search,
placeholder: 'Search by name or email...',
onChange: (value) => {
void setQueryState({ search: typeof value === 'string' ? value : '' });
},
onClear: () => {
void setQueryState({ search: '' });
},
},
{
key: 'status',
label: 'Status',
type: 'combobox',
value: queryState.status ? [queryState.status] : [],
options: [
{ label: 'Active', value: 'active' },
{ label: 'Invited', value: 'invited' },
],
onChange: (value) => {
const nextStatus = Array.isArray(value) ? value[0] ?? '' : '';
void setQueryState({ status: nextStatus });
},
onClear: () => {
void setQueryState({ status: '' });
},
},
],
[queryState.search, queryState.status, setQueryState],
);
const toolbarActions = (
<SmartFilterBar filters={smartFilters} className='min-w-0 flex-1' />
);
return (
<DataTable
columns={columns}
data={rows}
totalCount={rows.length}
toolbarActions={toolbarActions}
getRowId={(row) => row.id}
/>
);
}useDataTable Returns
tablestateparams({ page, pageSize, sorting, filters })selectedRows,selectedRowIds- helpers:
setPageIndex,setPageSize,setSorting,setColumnFilters,clearSelection,resetState
Batch Action Contract
batchActionComponent receives:
selectedRows: full selected row dataselectedRowIds: stable ids for actionsclearSelection(): clear selection state after operation
URL Sync (Nuqs)
- Keep compact keys:
page,size,sort,dir,search,status - Build
SmartFilterDefinition[]andDataTableApiParamsfrom URL state - Pass
initialStatetoDataTable - Update URL in
onStateChange