Documentation
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

DataTable

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:

  • sorting
  • columnFilters
  • pagination
  • rowSelection

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

  • table
  • state
  • params ({ page, pageSize, sorting, filters })
  • selectedRows, selectedRowIds
  • helpers: setPageIndex, setPageSize, setSorting, setColumnFilters, clearSelection, resetState

Batch Action Contract

batchActionComponent receives:

  • selectedRows: full selected row data
  • selectedRowIds: stable ids for actions
  • clearSelection(): clear selection state after operation

URL Sync (Nuqs)

  • Keep compact keys: page, size, sort, dir, search, status
  • Build SmartFilterDefinition[] and DataTableApiParams from URL state
  • Pass initialState to DataTable
  • Update URL in onStateChange

On this page