TanStack Table
A powerful data table component built on TanStack Table with support for sorting, filtering, pagination, resizable columns, and expandable rows.
Basic Usage
Get started with a minimal data table using the TanStack Table boilerplate. This example shows the essential configuration including column definitions and row data rendering.
Resource Name | Status |
|---|---|
| production-db-01 | Active |
| redis-cache-cluster | Active |
| user-uploads-bucket | Active |
| analytics-warehouse | Active |
| search-index-primary | Inactive |
import { Badge, LayerCard } from "@cloudflare/kumo";
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
import { useMemo } from "react";
import { TanstackTable } from "../blocks/TanstackTable";
export function TanstackBasicDemo() {
const data = useMemo(() => RESOURCE_DATA.slice(0, 5), []);
const columns = useMemo(() => {
const columnHelper = createColumnHelper<Resource>();
return [
columnHelper.accessor("resourceName", { header: "Resource Name" }),
columnHelper.accessor("status", {
header: "Status",
cell: (row) => <Badge>{row.getValue()}</Badge>,
}),
];
}, []);
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
enableColumnResizing: false,
enableSorting: false,
getRowId: (row) => row.id,
});
return (
<LayerCard>
<TanstackTable table={table} className="text-sm" />
</LayerCard>
);
} Loading State
Display a loading skeleton while data is being fetched. Use the LoadingTable component to show placeholder rows during async data loading.
Resource Name | Status |
|---|---|
import { Badge, LayerCard } from "@cloudflare/kumo";
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
import { useMemo } from "react";
import { TanstackTable } from "../blocks/TanstackTable";
export function TanstackLoadingDemo() {
const data = useMemo(() => [], []);
const columns = useMemo(() => {
const columnHelper = createColumnHelper<Resource>();
return [
columnHelper.accessor("resourceName", { header: "Resource Name" }),
columnHelper.accessor("status", {
header: "Status",
cell: (row) => <Badge>{row.getValue()}</Badge>,
}),
];
}, []);
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
enableColumnResizing: false,
enableSorting: false,
getRowId: (row) => row.id,
});
return (
<LayerCard>
<TanstackTable isLoading table={table} className="text-sm" />
</LayerCard>
);
} Resizable Columns
Enable column resizing by setting enableColumnResizing: true in the table options. Users can drag column borders to adjust widths. Refer to the TanStack column sizing guide for advanced configuration options.
Resource Name | Status | Size |
|---|---|---|
| production-db-01 | Active | 256 GB |
| redis-cache-cluster | Active | 64 GB |
| user-uploads-bucket | Active | 1.2 TB |
| analytics-warehouse | Active | 4.8 TB |
| search-index-primary | Inactive | 512 GB |
import { Badge, LayerCard } from "@cloudflare/kumo";
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
import { useMemo } from "react";
import { TanstackTable } from "../blocks/TanstackTable";
export function TanstackResizableDemo() {
const data = useMemo(() => RESOURCE_DATA.slice(0, 5), []);
const columns = useMemo(() => {
const columnHelper = createColumnHelper<Resource>();
return [
columnHelper.accessor("resourceName", { header: "Resource Name" }),
columnHelper.accessor("status", {
header: "Status",
cell: (row) => <Badge>{row.getValue()}</Badge>,
}),
columnHelper.accessor("size", { header: "Size" }),
];
}, []);
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
columnResizeMode: "onChange",
enableColumnResizing: true,
enableSorting: false,
getRowId: (row) => row.id,
});
return (
<LayerCard>
<TanstackTable table={table} className="text-sm" />
</LayerCard>
);
} For a smoother resizing experience, add a filler column that absorbs excess space. Configure minSize and size on columns to control their resizing behavior.
Resource Name | Status | Size | |
|---|---|---|---|
| production-db-01 | Active | 256 GB | |
| redis-cache-cluster | Active | 64 GB | |
| user-uploads-bucket | Active | 1.2 TB | |
| analytics-warehouse | Active | 4.8 TB | |
| search-index-primary | Inactive | 512 GB |
import { Badge, LayerCard } from "@cloudflare/kumo";
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
import { useMemo } from "react";
import { createFillerColumn, TanstackTable } from "../blocks/TanstackTable";
export function TanstackResizableFillerDemo() {
const data = useMemo(() => RESOURCE_DATA.slice(0, 5), []);
const columns = useMemo(() => {
const columnHelper = createColumnHelper<Resource>();
return [
columnHelper.accessor("resourceName", {
header: "Resource Name",
minSize: 100,
size: 300,
cell: (props) => (
<span className="line-clamp-1">{props.getValue()}</span>
),
}),
columnHelper.accessor("status", {
minSize: 100,
header: "Status",
cell: (row) => <Badge>{row.getValue()}</Badge>,
}),
columnHelper.accessor("size", { header: "Size", minSize: 100 }),
createFillerColumn<Resource>(),
];
}, []);
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
columnResizeMode: "onChange",
enableColumnResizing: true,
enableSorting: false,
getRowId: (row) => row.id,
});
return (
<LayerCard className="overflow-x-auto">
<TanstackTable table={table} className="text-sm" />
</LayerCard>
);
} Column Filtering
Add filter inputs to individual columns to let users narrow down data. See the TanStack filtering guide for filter function customization.
Resource Name | Status | Type | Size | Read | Write | |
|---|---|---|---|---|---|---|
| production-db-01 | Active | PostgreSQL | 256 GB | 2.4 MB/s | 1.1 MB/s | |
| redis-cache-cluster | Active | Redis | 64 GB | 45.2 MB/s | 38.7 MB/s | |
| user-uploads-bucket | Active | S3 | 1.2 TB | 8.5 MB/s | 4.2 MB/s | |
| analytics-warehouse | Active | ClickHouse | 4.8 TB | 156.3 MB/s | 89.4 MB/s | |
| search-index-primary | Inactive | Elasticsearch | 512 GB | 0 MB/s | 0 MB/s |
import { Badge, LayerCard } from "@cloudflare/kumo";
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
import { useMemo } from "react";
import { createFillerColumn, TanstackTable } from "../blocks/TanstackTable";
export function TanstackColumnFilterDemo() {
const data = useMemo(() => RESOURCE_DATA.slice(0, 5), []);
const columns = useMemo(() => {
const columnHelper = createColumnHelper<Resource>();
return [
columnHelper.accessor("resourceName", {
header: "Resource Name",
minSize: 100,
size: 300,
cell: (props) => (
<span className="line-clamp-1">{props.getValue()}</span>
),
}),
columnHelper.accessor("status", {
minSize: 100,
header: "Status",
cell: (row) => <Badge>{row.getValue()}</Badge>,
}),
columnHelper.accessor("type", { header: "Type", minSize: 100 }),
columnHelper.accessor("size", { header: "Size", minSize: 100 }),
columnHelper.accessor("read", { header: "Read", minSize: 100 }),
columnHelper.accessor("write", { header: "Write", minSize: 100 }),
createFillerColumn<Resource>(),
];
}, []);
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
columnResizeMode: "onChange",
enableColumnResizing: true,
enableSorting: false,
getRowId: (row) => row.id,
});
return (
<div className="space-y-2 max-w-full">
<TanstackTable.ColumnFilter table={table} />
<LayerCard className="overflow-x-auto">
<TanstackTable table={table} className="text-sm" />
</LayerCard>
</div>
);
} Client-side Pagination
Split large datasets into pages with built-in pagination controls. Configure page size and navigation options. Learn more in the TanStack pagination guide.
Resource Name | Status | Type | Size | Read | Write | |
|---|---|---|---|---|---|---|
| production-db-01 | Active | PostgreSQL | 256 GB | 2.4 MB/s | 1.1 MB/s | |
| redis-cache-cluster | Active | Redis | 64 GB | 45.2 MB/s | 38.7 MB/s | |
| user-uploads-bucket | Active | S3 | 1.2 TB | 8.5 MB/s | 4.2 MB/s | |
| analytics-warehouse | Active | ClickHouse | 4.8 TB | 156.3 MB/s | 89.4 MB/s | |
| search-index-primary | Inactive | Elasticsearch | 512 GB | 0 MB/s | 0 MB/s |
import { Badge, LayerCard } from "@cloudflare/kumo";
import { createColumnHelper, getCoreRowModel, getPaginationRowModel, useReactTable } from "@tanstack/react-table";
import { useMemo } from "react";
import { createFillerColumn, TanstackTable } from "../blocks/TanstackTable";
export function TanstackClientPaginationDemo() {
const data = useMemo(() => RESOURCE_DATA, []);
const columns = useMemo(() => {
const columnHelper = createColumnHelper<Resource>();
return [
columnHelper.accessor("resourceName", {
header: "Resource Name",
minSize: 100,
size: 300,
cell: (props) => (
<span className="line-clamp-1">{props.getValue()}</span>
),
}),
columnHelper.accessor("status", {
minSize: 100,
header: "Status",
cell: (row) => <Badge>{row.getValue()}</Badge>,
}),
columnHelper.accessor("type", { header: "Type", minSize: 100 }),
columnHelper.accessor("size", { header: "Size", minSize: 100 }),
columnHelper.accessor("read", { header: "Read", minSize: 100 }),
columnHelper.accessor("write", { header: "Write", minSize: 100 }),
createFillerColumn<Resource>(),
];
}, []);
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
columnResizeMode: "onChange",
enableColumnResizing: true,
enableSorting: false,
getPaginationRowModel: getPaginationRowModel(),
initialState: {
pagination: {
pageIndex: 0,
pageSize: 5,
},
},
getRowId: (row) => row.id,
});
return (
<div className="space-y-2 max-w-full">
<TanstackTable.ColumnFilter table={table} />
<LayerCard className="overflow-x-auto">
<TanstackTable table={table} className="text-sm" />
</LayerCard>
<TanstackTable.ClientPagination
table={table}
pageSizeOptions={[5, 10, 15]}
/>
</div>
);
} Sorting
Enable click-to-sort on column headers. Supports multi-column sorting and custom sort functions. Check the TanStack sorting guide for implementation details.
| production-db-01 | Active | PostgreSQL | 256 GB | 2.4 MB/s | 1.1 MB/s | |
| redis-cache-cluster | Active | Redis | 64 GB | 45.2 MB/s | 38.7 MB/s | |
| user-uploads-bucket | Active | S3 | 1.2 TB | 8.5 MB/s | 4.2 MB/s | |
| analytics-warehouse | Active | ClickHouse | 4.8 TB | 156.3 MB/s | 89.4 MB/s | |
| search-index-primary | Inactive | Elasticsearch | 512 GB | 0 MB/s | 0 MB/s |
import { Badge, LayerCard } from "@cloudflare/kumo";
import { createColumnHelper, getCoreRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
import { useMemo } from "react";
import { createFillerColumn, TanstackTable } from "../blocks/TanstackTable";
export function TanstackSortingDemo() {
const data = useMemo(() => RESOURCE_DATA, []);
const columns = useMemo(() => {
const columnHelper = createColumnHelper<Resource>();
return [
columnHelper.accessor("resourceName", {
header: "Resource Name",
minSize: 100,
size: 300,
cell: (props) => (
<span className="line-clamp-1">{props.getValue()}</span>
),
}),
columnHelper.accessor("status", {
minSize: 100,
header: "Status",
cell: (row) => <Badge>{row.getValue()}</Badge>,
}),
columnHelper.accessor("type", { header: "Type", minSize: 100 }),
columnHelper.accessor("size", { header: "Size", minSize: 100 }),
columnHelper.accessor("read", { header: "Read", minSize: 100 }),
columnHelper.accessor("write", { header: "Write", minSize: 100 }),
createFillerColumn<Resource>(),
];
}, []);
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
columnResizeMode: "onChange",
enableColumnResizing: true,
getSortedRowModel: getSortedRowModel(),
enableSorting: true,
getPaginationRowModel: getPaginationRowModel(),
initialState: {
pagination: {
pageIndex: 0,
pageSize: 5,
},
},
getRowId: (row) => row.id,
});
return (
<div className="space-y-2 max-w-full">
<TanstackTable.ColumnFilter table={table} />
<LayerCard className="overflow-x-auto">
<TanstackTable table={table} className="text-sm" />
</LayerCard>
<TanstackTable.ClientPagination
table={table}
pageSizeOptions={[5, 10, 15]}
/>
</div>
);
} Actions Column
Add an actions column with buttons or dropdowns for row-level operations like edit, delete, or view details.
| production-db-01 | Active | PostgreSQL | 256 GB | 2.4 MB/s | 1.1 MB/s | ||
| redis-cache-cluster | Active | Redis | 64 GB | 45.2 MB/s | 38.7 MB/s | ||
| user-uploads-bucket | Active | S3 | 1.2 TB | 8.5 MB/s | 4.2 MB/s | ||
| analytics-warehouse | Active | ClickHouse | 4.8 TB | 156.3 MB/s | 89.4 MB/s | ||
| search-index-primary | Inactive | Elasticsearch | 512 GB | 0 MB/s | 0 MB/s |
import { Badge, Button, DropdownMenu, LayerCard } from "@cloudflare/kumo";
import { createColumnHelper, getCoreRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
import { useMemo } from "react";
import { createActionsColumn, createFillerColumn, TanstackTable } from "../blocks/TanstackTable";
import { DotsThreeIcon } from "@phosphor-icons/react";
export function TanstackActionsDemo() {
const data = useMemo(() => RESOURCE_DATA, []);
const columns = useMemo(() => {
const columnHelper = createColumnHelper<Resource>();
return [
columnHelper.accessor("resourceName", {
header: "Resource Name",
minSize: 100,
size: 300,
cell: (props) => (
<span className="line-clamp-1">{props.getValue()}</span>
),
}),
columnHelper.accessor("status", {
minSize: 100,
header: "Status",
cell: (row) => <Badge>{row.getValue()}</Badge>,
}),
columnHelper.accessor("type", { header: "Type", minSize: 100 }),
columnHelper.accessor("size", { header: "Size", minSize: 100 }),
columnHelper.accessor("read", { header: "Read", minSize: 100 }),
columnHelper.accessor("write", { header: "Write", minSize: 100 }),
createFillerColumn<Resource>(),
createActionsColumn<Resource>({
cell: (row) => {
return (
<DropdownMenu>
<DropdownMenu.Trigger
render={
<Button
variant="ghost"
size="sm"
shape="square"
aria-label="More"
>
<DotsThreeIcon weight="bold" size={20} />
</Button>
}
/>
<DropdownMenu.Content>
<DropdownMenu.Item
onClick={() => {
alert("Removing " + row.row.original.resourceName);
}}
>
Remove
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
);
},
}),
];
}, []);
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
columnResizeMode: "onChange",
enableColumnResizing: true,
getSortedRowModel: getSortedRowModel(),
enableSorting: true,
getPaginationRowModel: getPaginationRowModel(),
initialState: {
pagination: {
pageIndex: 0,
pageSize: 5,
},
},
getRowId: (row) => row.id,
});
return (
<div className="space-y-2 max-w-full">
<TanstackTable.ColumnFilter table={table} />
<LayerCard className="overflow-x-auto relative">
<TanstackTable table={table} className="text-sm" />
</LayerCard>
<TanstackTable.ClientPagination
table={table}
pageSizeOptions={[5, 10, 15]}
/>
</div>
);
} Expandable Subrows
Display hierarchical data with expandable rows. Click the chevron to reveal nested subrow information.
Resource Name | Status | Type | Size | Read | Write | |
|---|---|---|---|---|---|---|
| production-cluster-us | Active | Kubernetes | 2.4 TB | 850 MB/s | 420 MB/s | |
| analytics-pipeline | Active | Apache Spark | 8.5 TB | 2.1 GB/s | 1.8 GB/s | |
| search-cluster-eu | Active | Elasticsearch | 1.2 TB | 450 MB/s | 120 MB/s | |
| message-bus-primary | Active | Apache Kafka | 4.2 TB | 1.5 GB/s | 1.2 GB/s | |
| cache-tier-global | Active | Redis Cluster | 512 GB | 8.5 GB/s | 6.2 GB/s |
import { Badge, LayerCard } from "@cloudflare/kumo";
import { createColumnHelper, getCoreRowModel, getExpandedRowModel, useReactTable } from "@tanstack/react-table";
import { useMemo } from "react";
import { TanstackTable } from "../blocks/TanstackTable";
export function TanstackSubRowDemo() {
const columns = useMemo(() => {
const columnHelper = createColumnHelper<ResourceWithSubrows>();
return [
columnHelper.accessor("resourceName", {
header: "Resource Name",
minSize: 100,
size: 300,
cell: (props) => (
<span className="line-clamp-1">{props.getValue()}</span>
),
}),
columnHelper.accessor("status", {
minSize: 100,
header: "Status",
cell: (row) => <Badge>{row.getValue()}</Badge>,
}),
columnHelper.accessor("type", { header: "Type", minSize: 100 }),
columnHelper.accessor("size", { header: "Size", minSize: 100 }),
columnHelper.accessor("read", { header: "Read", minSize: 100 }),
columnHelper.accessor("write", { header: "Write", minSize: 100 }),
];
}, []);
const table = useReactTable({
columns,
data: RESOURCE_DATA_WITH_SUBROWS,
getCoreRowModel: getCoreRowModel(),
columnResizeMode: "onChange",
enableColumnResizing: false,
enableSorting: false,
enableExpanding: true,
getExpandedRowModel: getExpandedRowModel(),
getSubRows: (row) => row.subRows,
getRowId: (row) => row.id,
});
return (
<div className="space-y-2 max-w-full">
<TanstackTable.ColumnFilter table={table} />
<LayerCard className="overflow-x-auto relative">
<TanstackTable table={table} showExpandControl={true} className="text-sm" />
</LayerCard>
</div>
);
} Custom Expand Component
Replace the default subrow with a fully custom component when expanding a row. Useful for displaying detailed views, forms, or related data.
Resource Name | Status | Type | Size | Read | Write | |
|---|---|---|---|---|---|---|
| production-cluster-us | Active | Kubernetes | 2.4 TB | 850 MB/s | 420 MB/s | |
| analytics-pipeline | Active | Apache Spark | 8.5 TB | 2.1 GB/s | 1.8 GB/s | |
| search-cluster-eu | Active | Elasticsearch | 1.2 TB | 450 MB/s | 120 MB/s | |
| message-bus-primary | Active | Apache Kafka | 4.2 TB | 1.5 GB/s | 1.2 GB/s | |
| cache-tier-global | Active | Redis Cluster | 512 GB | 8.5 GB/s | 6.2 GB/s |
import { Badge, LayerCard } from "@cloudflare/kumo";
import { createColumnHelper, getCoreRowModel, useReactTable, Row } from "@tanstack/react-table";
import { useCallback, useMemo } from "react";
import { TanstackTable } from "../blocks/TanstackTable";
export function TanstackExpandCustomDemo() {
const columns = useMemo(() => {
const columnHelper = createColumnHelper<Resource>();
return [
columnHelper.accessor("resourceName", {
header: "Resource Name",
minSize: 100,
size: 300,
cell: (props) => (
<span className="line-clamp-1">{props.getValue()}</span>
),
}),
columnHelper.accessor("status", {
minSize: 100,
header: "Status",
cell: (row) => <Badge>{row.getValue()}</Badge>,
}),
columnHelper.accessor("type", { header: "Type", minSize: 100 }),
columnHelper.accessor("size", { header: "Size", minSize: 100 }),
columnHelper.accessor("read", { header: "Read", minSize: 100 }),
columnHelper.accessor("write", { header: "Write", minSize: 100 }),
];
}, []);
const table = useReactTable({
columns,
data: RESOURCE_DATA_WITH_SUBROWS,
getCoreRowModel: getCoreRowModel(),
columnResizeMode: "onChange",
enableColumnResizing: false,
enableSorting: false,
enableExpanding: true,
getRowCanExpand: () => true,
getRowId: (row) => row.id,
});
const customRenderer = useCallback((row: Row<Resource>) => {
return (
<div className="p-4">
This is custom component for {row.original.resourceName}
</div>
);
}, []);
return (
<div className="space-y-2 max-w-full">
<TanstackTable.ColumnFilter table={table} />
<LayerCard className="overflow-x-auto relative">
<TanstackTable
table={table}
showExpandControl
customExpandChildren={customRenderer}
className="text-sm"
/>
</LayerCard>
</div>
);
} Selection
Enable row selection with checkboxes for bulk operations. Access selected rows via the table state to perform actions like delete, export, or bulk edit on multiple items at once. See the TanStack row selection guide for implementation details.
Resource Name | Status | Type | Size | Read | Write | |
|---|---|---|---|---|---|---|
| production-cluster-us | Active | Kubernetes | 2.4 TB | 850 MB/s | 420 MB/s | |
| analytics-pipeline | Active | Apache Spark | 8.5 TB | 2.1 GB/s | 1.8 GB/s | |
| search-cluster-eu | Active | Elasticsearch | 1.2 TB | 450 MB/s | 120 MB/s | |
| message-bus-primary | Active | Apache Kafka | 4.2 TB | 1.5 GB/s | 1.2 GB/s | |
| cache-tier-global | Active | Redis Cluster | 512 GB | 8.5 GB/s | 6.2 GB/s |
import { Badge, LayerCard } from "@cloudflare/kumo";
import { createColumnHelper, getCoreRowModel, useReactTable, RowSelectionState } from "@tanstack/react-table";
import { useMemo, useState } from "react";
import { TanstackTable } from "../blocks/TanstackTable";
export function TanstackSelectionDemo() {
const [selection, setSelection] = useState<RowSelectionState>({});
const columns = useMemo(() => {
const columnHelper = createColumnHelper<Resource>();
return [
columnHelper.accessor("resourceName", {
header: "Resource Name",
minSize: 100,
size: 300,
cell: (props) => (
<span className="line-clamp-1">{props.getValue()}</span>
),
}),
columnHelper.accessor("status", {
minSize: 100,
header: "Status",
cell: (row) => <Badge>{row.getValue()}</Badge>,
}),
columnHelper.accessor("type", { header: "Type", minSize: 100 }),
columnHelper.accessor("size", { header: "Size", minSize: 100 }),
columnHelper.accessor("read", { header: "Read", minSize: 100 }),
columnHelper.accessor("write", { header: "Write", minSize: 100 }),
];
}, []);
const table = useReactTable({
columns,
data: RESOURCE_DATA_WITH_SUBROWS,
getCoreRowModel: getCoreRowModel(),
columnResizeMode: "onChange",
enableColumnResizing: false,
enableSorting: false,
enableRowSelection: true,
getRowId: (row) => row.id,
onRowSelectionChange: setSelection,
state: {
rowSelection: selection,
},
});
return (
<div className="space-y-2 max-w-full">
<TanstackTable.ColumnFilter table={table} />
<LayerCard className="overflow-x-auto relative">
<TanstackTable table={table} showSelectionControl className="text-sm" />
</LayerCard>
</div>
);
}