Appearance
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
getRowIdfor stable row identity with selection - Use
storageKeyfor 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
totalCountin server mode - Don't use for small datasets (< 10 rows)
- Don't skip accessibility (provide proper labels)
Related Components β
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 β
Full-Featured Example β
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 β
- SimpleSortableTable - Lightweight alternative for simple tables
- SimpleSortableTable API - API reference
- TablePagination API - Standalone pagination
- EditableTableCell API - Inline editing
- TanStack Table Documentation
- shadcn/ui Table
- Inline Editable
- Bulk Selection Pattern