Skip to content

DataTable ​

A production-ready, feature-rich data table component built on TanStack Table v8. Handles everything from simple lists to complex data grids with sorting, filtering, pagination, and more.

Overview ​

The DataTable component is your go-to solution for displaying tabular data in admin interfaces. It automatically detects whether you need client-side or server-side processing, and provides a comprehensive toolbar with search, filters, column management, and export capabilitiesβ€”all out of the box.

Key Features ​

  • πŸ”„ Dual Mode: Automatic client/server mode detection
  • πŸ” Smart Filtering: Text and select filters per column + global search
  • πŸ“Š Flexible Sorting: Multi-column sorting with visual indicators
  • πŸ“„ Pagination: Page-based navigation with customizable page sizes
  • βœ… Row Selection: Bulk selection with callbacks for batch operations
  • πŸ‘οΈ Column Management: Show/hide columns and resize by dragging
  • πŸ’Ύ State Persistence: Save user preferences to localStorage
  • πŸ“± Mobile Responsive: Automatic card view on mobile devices
  • ⚑ Virtual Scrolling: High performance with large datasets (50+ rows)
  • πŸ“€ CSV Export: One-click export of visible data
  • 🎨 Density Control: Compact, normal, or spacious row heights
  • β™Ώ Accessible: ARIA labels and keyboard navigation

When to Use ​

Use DataTable when you need: ​

βœ… Admin lists with many records (users, orders, products)
βœ… Data exploration with sorting, filtering, and search
βœ… Bulk operations with row selection
βœ… Server-side pagination for large datasets
βœ… Column customization (visibility, resizing, reordering)
βœ… State persistence across sessions
βœ… CSV export functionality

Consider alternatives for: ​

❌ Simple read-only tables β†’ Use SimpleSortableTable
❌ Small datasets (< 20 rows) β†’ Use SimpleSortableTable
❌ Custom layouts β†’ Build with shadcn/ui Table primitives


Quick Start ​

Basic Example ​

tsx
import { DataTable, type DataTableColumn } from '@/shared/ui/components/table/DataTable'

type User = {
  id: number
  name: string
  email: string
  role: string
  status: 'Active' | 'Inactive'
}

const columns: DataTableColumn<User>[] = [
  {
    id: 'name',
    header: 'Name',
    cell: (row) => <span className="font-medium">{row.name}</span>,
    sortable: true,
  },
  {
    id: 'email',
    header: 'Email',
    cell: (row) => row.email,
    sortable: true,
  },
  {
    id: 'role',
    header: 'Role',
    cell: (row) => row.role,
  },
  {
    id: 'status',
    header: 'Status',
    cell: (row) => (
      <Badge variant={row.status === 'Active' ? 'default' : 'secondary'}>
        {row.status}
      </Badge>
    ),
  },
]

export function UserTable({ users }: { users: User[] }) {
  return (
    <DataTable
      columns={columns}
      data={users}
      pageSizeOptions={[10, 25, 50]}
      defaultPageSize={10}
    />
  )
}

That's it! You now have a fully functional table with:

  • Global search
  • Column sorting
  • Pagination
  • Column visibility toggle
  • Density selector
  • CSV export

Core Concepts ​

Client vs Server Mode ​

DataTable automatically detects the mode based on which callbacks you provide:

Client Mode (default):

tsx
// No callbacks = client-side processing
<DataTable columns={columns} data={data} />

Server Mode:

tsx
// Provide callbacks = server-side processing
<DataTable
  columns={columns}
  data={data}
  mode="server"
  totalCount={totalRecords}
  onSortingChange={(sorting) => fetchData({ sorting })}
  onColumnFiltersChange={(filters) => fetchData({ filters })}
  onPaginationChange={(pagination) => fetchData({ pagination })}
/>

Column Definitions ​

Columns are defined using the DataTableColumn<T> interface:

tsx
const columns: DataTableColumn<User>[] = [
  {
    id: 'name',                    // Unique identifier
    header: 'Full Name',           // Column header (string or ReactNode)
    cell: (row) => row.name,       // Cell renderer function
    accessorKey: 'name',           // Optional: for sorting/filtering
    sortable: true,                // Enable sorting
    width: 200,                    // Initial width in pixels
    minWidth: 150,                 // Minimum width when resizing
    visible: true,                 // Initial visibility
    filter: {                      // Column filter configuration
      type: 'text',
      placeholder: 'Search names...'
    }
  },
]

Features in Detail ​

1. Column Filtering ​

Add filters to individual columns for granular data exploration:

tsx
const columns: DataTableColumn<User>[] = [
  {
    id: 'name',
    header: 'Name',
    cell: (row) => row.name,
    sortable: true,
    filter: {
      type: 'text',
      placeholder: 'Search names...'
    }
  },
  {
    id: 'role',
    header: 'Role',
    cell: (row) => row.role,
    filter: {
      type: 'select',
      options: [
        { label: 'Admin', value: 'Admin' },
        { label: 'Manager', value: 'Manager' },
        { label: 'Member', value: 'Member' }
      ]
    }
  },
]

Filter Types:

  • text: Fuzzy text search (client) or debounced search (server)
  • select: Exact match dropdown filter

2. Row Selection ​

Enable checkboxes for bulk operations:

tsx
function UserManagement() {
  const [selectedUsers, setSelectedUsers] = useState<User[]>([])

  return (
    <>
      {selectedUsers.length > 0 && (
        <div className="mb-4 p-3 bg-primary/10 rounded-lg">
          <span>{selectedUsers.length} users selected</span>
          <Button onClick={() => bulkDelete(selectedUsers)}>
            Delete Selected
          </Button>
        </div>
      )}
      
      <DataTable
        columns={columns}
        data={users}
        enableRowSelection
        onRowSelectionChange={setSelectedUsers}
        selectedRows={selectedUsers}
      />
    </>
  )
}

3. Column Visibility & Resizing ​

Users can customize which columns to show and adjust widths:

tsx
const columns: DataTableColumn<User>[] = [
  { id: 'name', header: 'Name', width: 200, visible: true },
  { id: 'email', header: 'Email', width: 250, visible: true },
  { id: 'phone', header: 'Phone', visible: false }, // Hidden by default
  { id: 'address', header: 'Address', visible: false },
]

<DataTable
  columns={columns}
  data={users}
  enableColumnVisibility  // Show column toggle in toolbar
  enableColumnResizing    // Enable drag-to-resize
/>

4. State Persistence ​

Save user preferences across sessions:

tsx
<DataTable
  columns={columns}
  data={users}
  storageKey="users-table-v1"
  // Automatically persists:
  // - Sorting state
  // - Column filters
  // - Column visibility
  // - Column sizing
  // - Density preference
  // - Global filter
/>

TIP

Change the storageKey version (e.g., v1 β†’ v2) when you update column definitions to reset user preferences.

5. Virtual Scrolling ​

For large datasets, virtual scrolling renders only visible rows:

tsx
<DataTable
  columns={columns}
  data={largeDataset}
  enableVirtualScroll      // Auto-enabled for 50+ rows
  virtualScrollHeight={600} // Max height in pixels
  virtualScrollOverscan={10} // Rows to render outside viewport
/>

Performance: Renders 1000+ rows smoothly by only rendering ~20 visible rows at a time.

6. Row Actions ​

Add action buttons to each row:

tsx
<DataTable
  columns={columns}
  data={users}
  rowActions={(user) => (
    <div className="flex gap-2">
      <Button size="sm" onClick={() => edit(user)}>Edit</Button>
      <Button size="sm" variant="destructive" onClick={() => remove(user)}>
        Delete
      </Button>
    </div>
  )}
  pinActions="right" // Sticky actions column
/>

7. Custom Empty/Loading States ​

tsx
<DataTable
  columns={columns}
  data={users}
  isLoading={isLoading}
  isError={isError}
  loadingContent={<CustomSpinner />}
  errorContent={<ErrorMessage />}
  emptyContent={
    <div className="text-center py-12">
      <p>No users found</p>
      <Button onClick={createUser}>Create First User</Button>
    </div>
  }
/>

8. Internationalization ​

Customize all labels:

tsx
<DataTable
  columns={columns}
  data={users}
  labels={{
    loading: 'Cargando...',
    noResults: 'No se encontraron resultados',
    errorMessage: 'Error al cargar datos',
    actions: 'Acciones',
    clearFilters: 'Limpiar filtros',
    exportCsv: 'Exportar CSV',
    columns: 'Columnas',
    search: 'Buscar...',
    selectFilter: 'Seleccionar...',
  }}
/>

Advanced Usage ​

Server-Side Example ​

tsx
function ServerSideTable() {
  const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 })
  const [sorting, setSorting] = useState<SortingState>([])
  const [filters, setFilters] = useState<ColumnFiltersState>([])

  const { data, isLoading, error } = useQuery({
    queryKey: ['users', pagination, sorting, filters],
    queryFn: () => fetchUsers({ pagination, sorting, filters }),
  })

  return (
    <DataTable
      columns={columns}
      data={data?.users ?? []}
      mode="server"
      totalCount={data?.total}
      isLoading={isLoading}
      isError={!!error}
      onPaginationChange={setPagination}
      onSortingChange={setSorting}
      onColumnFiltersChange={setFilters}
    />
  )
}

Custom CSV Export ​

tsx
<DataTable
  columns={columns}
  data={users}
  onExportCsv={() => {
    // Custom export logic
    const csv = generateCustomCsv(users)
    downloadFile(csv, 'users-export.csv')
  }}
/>

Mobile Customization ​

Customize how data appears on mobile:

tsx
const columns: DataTableColumn<User>[] = [
  {
    id: 'name',
    header: 'Name',
    cell: (row) => row.name,
    mobileRender: (row) => (
      <div>
        <div className="font-bold">{row.name}</div>
        <div className="text-sm text-muted-foreground">{row.email}</div>
      </div>
    ),
  },
]

Best Practices ​

βœ… Do's ​

  • Use memoized columns to prevent unnecessary re-renders
  • Provide getRowId for stable row identity with selection
  • Use storageKey for better UX with state persistence
  • Enable virtual scrolling for datasets with 50+ rows
  • Provide loading states for async data
  • Use server mode for datasets with 1000+ total records

❌ Don'ts ​

  • Don't recreate columns on every render (use useMemo)
  • Don't mix client and server modes (choose one)
  • Don't forget totalCount in server mode
  • Don't use for small datasets (< 10 rows)
  • Don't skip accessibility (provide proper labels)

SimpleSortableTable ​

For simpler use cases without the full feature set:

tsx
import { SimpleSortableTable } from '@/shared/ui/components/table/SimpleSortableTable'
import { type ColumnDef } from '@tanstack/react-table'

const columns: ColumnDef<Task>[] = [
  { accessorKey: 'title', header: 'Title' },
  { accessorKey: 'status', header: 'Status', enableSorting: true },
]

<SimpleSortableTable
  columns={columns}
  data={tasks}
  density="normal"
  onRowClick={(task) => navigate(`/tasks/${task.id}`)}
/>

When to use:

  • Client-side only
  • No filtering or complex features needed
  • Simple sorting requirements
  • Smaller datasets

EditableTableCell ​

For inline editing within tables:

tsx
import { EditableTableCell } from '@/shared/ui/components/table/EditableTableCell'

const columns: DataTableColumn<Product>[] = [
  {
    id: 'price',
    header: 'Price',
    cell: (row) => (
      <EditableTableCell
        value={row.price}
        type="number"
        prefix="$"
        onSave={(newPrice) => updateProduct(row.id, { price: newPrice })}
      />
    ),
  },
]

TablePagination ​

Standalone pagination component:

tsx
import { TablePagination } from '@/shared/ui/components/table/TablePagination'

<TablePagination
  pageIndex={0}
  pageSize={10}
  totalCount={100}
  pageSizeOptions={[10, 25, 50]}
  onPageChange={(index) => setPage(index)}
  onPageSizeChange={(size) => setPageSize(size)}
/>

API Reference ​

For detailed prop documentation, see:


Examples ​

tsx
<DataTable
  columns={columns}
  data={users}
  // Features
  enableRowSelection
  enableGlobalSearch
  enableColumnVisibility
  enableColumnResizing
  enableDensitySelector
  enableCsvExport
  enableVirtualScroll
  // Pagination
  pageSizeOptions={[10, 25, 50, 100]}
  defaultPageSize={25}
  // Persistence
  storageKey="users-table-v2"
  // Callbacks
  onRowSelectionChange={(rows) => setSelected(rows)}
  // Styling
  density="normal"
  className="my-custom-table"
/>

Minimal Example ​

tsx
<DataTable columns={columns} data={users} />

Troubleshooting ​

Filters not working in server mode ​

Make sure you're providing the onColumnFiltersChange callback:

tsx
<DataTable
  mode="server"
  onColumnFiltersChange={(filters) => {
    // Update your query params
    refetch({ filters })
  }}
/>

Selection not clearing ​

Use the selectedRows prop to control selection externally:

tsx
const [selected, setSelected] = useState<User[]>([])

<DataTable
  enableRowSelection
  selectedRows={selected}
  onRowSelectionChange={setSelected}
/>

// Clear selection
<Button onClick={() => setSelected([])}>Clear</Button>

Performance issues with large datasets ​

Enable virtual scrolling:

tsx
<DataTable
  enableVirtualScroll
  virtualScrollHeight={600}
/>

Migration Guide ​

From old DataTable ​

The component now uses DataTableColumn instead of DTColumn:

tsx
// Old
import { DataTable, type DTColumn } from '@/shared/ui/components/table/DataTable'
const columns: DTColumn<User>[] = [...]

// New
import { DataTable, type DataTableColumn } from '@/shared/ui/components/table/DataTable'
const columns: DataTableColumn<User>[] = [...]

See Also ​