TanStack Table
@cloudflare/kumo

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-01Active
redis-cache-clusterActive
user-uploads-bucketActive
analytics-warehouseActive
search-index-primaryInactive
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-01Active256 GB
redis-cache-clusterActive64 GB
user-uploads-bucketActive1.2 TB
analytics-warehouseActive4.8 TB
search-index-primaryInactive512 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-01Active256 GB
redis-cache-clusterActive64 GB
user-uploads-bucketActive1.2 TB
analytics-warehouseActive4.8 TB
search-index-primaryInactive512 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-01ActivePostgreSQL256 GB2.4 MB/s1.1 MB/s
redis-cache-clusterActiveRedis64 GB45.2 MB/s38.7 MB/s
user-uploads-bucketActiveS31.2 TB8.5 MB/s4.2 MB/s
analytics-warehouseActiveClickHouse4.8 TB156.3 MB/s89.4 MB/s
search-index-primaryInactiveElasticsearch512 GB0 MB/s0 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-01ActivePostgreSQL256 GB2.4 MB/s1.1 MB/s
redis-cache-clusterActiveRedis64 GB45.2 MB/s38.7 MB/s
user-uploads-bucketActiveS31.2 TB8.5 MB/s4.2 MB/s
analytics-warehouseActiveClickHouse4.8 TB156.3 MB/s89.4 MB/s
search-index-primaryInactiveElasticsearch512 GB0 MB/s0 MB/s
Showing 1-5 of 30
Per page:
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-01ActivePostgreSQL256 GB2.4 MB/s1.1 MB/s
redis-cache-clusterActiveRedis64 GB45.2 MB/s38.7 MB/s
user-uploads-bucketActiveS31.2 TB8.5 MB/s4.2 MB/s
analytics-warehouseActiveClickHouse4.8 TB156.3 MB/s89.4 MB/s
search-index-primaryInactiveElasticsearch512 GB0 MB/s0 MB/s
Showing 1-5 of 30
Per page:
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-01ActivePostgreSQL256 GB2.4 MB/s1.1 MB/s
redis-cache-clusterActiveRedis64 GB45.2 MB/s38.7 MB/s
user-uploads-bucketActiveS31.2 TB8.5 MB/s4.2 MB/s
analytics-warehouseActiveClickHouse4.8 TB156.3 MB/s89.4 MB/s
search-index-primaryInactiveElasticsearch512 GB0 MB/s0 MB/s
Showing 1-5 of 30
Per page:
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-usActiveKubernetes2.4 TB850 MB/s420 MB/s
analytics-pipelineActiveApache Spark8.5 TB2.1 GB/s1.8 GB/s
search-cluster-euActiveElasticsearch1.2 TB450 MB/s120 MB/s
message-bus-primaryActiveApache Kafka4.2 TB1.5 GB/s1.2 GB/s
cache-tier-globalActiveRedis Cluster512 GB8.5 GB/s6.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-usActiveKubernetes2.4 TB850 MB/s420 MB/s
analytics-pipelineActiveApache Spark8.5 TB2.1 GB/s1.8 GB/s
search-cluster-euActiveElasticsearch1.2 TB450 MB/s120 MB/s
message-bus-primaryActiveApache Kafka4.2 TB1.5 GB/s1.2 GB/s
cache-tier-globalActiveRedis Cluster512 GB8.5 GB/s6.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-usActiveKubernetes2.4 TB850 MB/s420 MB/s
analytics-pipelineActiveApache Spark8.5 TB2.1 GB/s1.8 GB/s
search-cluster-euActiveElasticsearch1.2 TB450 MB/s120 MB/s
message-bus-primaryActiveApache Kafka4.2 TB1.5 GB/s1.2 GB/s
cache-tier-globalActiveRedis Cluster512 GB8.5 GB/s6.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>
  );
}