Table System Guide
Feature-complete DataTable with search, sort, filter, export, and bulk actions.
Table System Guide
The DataTable component handles every table use case in SentinelGrid: search, sort, filter, pagination, column visibility, bulk actions, and automatic mobile-card rendering — all from a single column config.
Overview
The DataTable is fully generic over your row type. You pass a columns array and a data array, plus optional callbacks for row clicks and bulk actions. Everything else — search box, filter dropdowns, sort headers, pagination — is built in.
Features
Search
Client-side fuzzy search across any subset of keys.
Filter
Per-column filter dropdowns with custom predicates.
Column visibility
Show or hide columns via a dropdown checklist.
Export
Bulk actions can trigger CSV, JSON, or custom export flows.
Sortable
Click any header to toggle asc/desc. Initial sort supported.
Mobile cards
Rows automatically render as cards on small viewports.
Live Example
Below is a DataTable wired to the mock incidents dataset. Try searching, sorting by clicking headers, filtering severity or status, paginating, and resizing the browser to see mobile cards kick in.
ID | Title | Severity | Status | Service | |
|---|---|---|---|---|---|
| inc1 | Checkout API elevated 5xx rate in us-east-1 | High | Investigating | s6, s2 | |
| inc2 | Billing invoice generation backlog | Medium | Monitoring | s2 | |
| inc3 | Auth service token validation errors in EU | Low | Resolved | s3 | |
| inc4 | Web app slow page loads (LCP regression) | Medium | Identified | s8 |
Checkout API elevated 5xx rate in us-east-1
Billing invoice generation backlog
Auth service token validation errors in EU
Web app slow page loads (LCP regression)
Column Configuration
Each column is defined by a config object. The cell function returns JSX, sortValue enables sorting, and filterable + filterFn enable the filter dropdown.
const columns: Column<Incident>[] = [
{
key: "id",
header: "ID",
cell: (row) => <span className="font-mono text-xs">{row.id}</span>,
sortable: true,
sortValue: (row) => row.id,
width: "w-24",
},
{
key: "severity",
header: "Severity",
cell: (row) => <SeverityBadge severity={row.severity} />,
sortable: true,
sortValue: (row) => row.severity,
filterable: true,
filterOptions: [
{ label: "Critical", value: "critical" },
{ label: "High", value: "high" },
],
filterFn: (row, value) => row.severity === value,
hideOnMobile: true,
},
];DataTable props
| Prop | Type | Description |
|---|---|---|
| columns | Column<T>[] | Column definitions |
| data | T[] | Rows to render |
| rowKey | (row: T) => string | Stable key extractor |
| searchKeys | (keyof T)[] | Keys included in client-side search |
| pageSize | number | Rows per page (default 10) |
| enableSelection | boolean | Show checkbox column |
| onRowClick | (row: T) => void | Row click handler |
| bulkActions | BulkAction[] | Actions shown when rows are selected |
| mobileCard | (row: T) => ReactNode | Custom card render for mobile |
| stickyHeader | boolean | Sticky table header (default true) |
Mobile Cards
On viewports below the md breakpoint, the table body is replaced with a vertical card list. Provide a mobileCard render function to control the layout. Columns marked hideOnMobile: true are hidden in the table view but can still appear in your card.
const mobileCard = (row: Incident) => (
<div className="p-3 space-y-1">
<div className="flex items-center justify-between">
<span className="font-mono text-xs text-primary">{row.id}</span>
<SeverityBadge severity={row.severity} size="sm" />
</div>
<p className="text-sm font-medium truncate">{row.title}</p>
<IncidentStatusBadge status={row.status} />
</div>
);
<DataTable columns={columns} data={data} rowKey={(r) => r.id} mobileCard={mobileCard} />Bulk Actions
When enableSelection is true and at least one row is checked, a bulk action bar appears above the table. Pass an array of actions; each receives the selected rows.
<DataTable
columns={columns}
data={data}
rowKey={(r) => r.id}
enableSelection
bulkActions={[
{ label: "Export CSV", icon: <Download className="w-3.5 h-3.5" />, onClick: (rows) => exportCsv(rows) },
{ label: "Acknowledge", onClick: (rows) => acknowledge(rows) },
{ label: "Close", onClick: (rows) => closeIncidents(rows), variant: "destructive" },
]}
/>Pagination
pageSize. The footer shows the current range and total count, plus prev/next controls.