Skip to Content
ComponentsData Table

Data Table

A recipe for building data tables using TanStack Table  with chunks-ui’s Table component for styling. TanStack handles all the logic (sorting, filtering, pagination, row selection) while Table.* handles the look and feel.

Dependencies

bun add @tanstack/react-table

Full Example

This example shows sorting, filtering, pagination, and row selection — all composed from chunks-ui primitives (Table, Button, Input, Checkbox).

Status
Amount
successalice@example.com
$316.00
successbob@example.com
$242.00
processingcharlie@example.com
$837.00
successdiana@example.com
$874.00
failedeve@example.com
$721.00
pendingfrank@example.com
$150.00
successgrace@example.com
$490.00
processinghank@example.com
$203.00
0 of 8 row(s) selected.

How It Works

1. Define Columns

Use TanStack’s ColumnDef type. Render chunks-ui components inside header and cell:

import { type ColumnDef } from "@tanstack/react-table"; import { Button, Checkbox } from "chunks-ui"; const columns: ColumnDef<Payment>[] = [ { id: "select", header: ({ table }) => ( <Checkbox.Root checked={table.getIsAllPageRowsSelected()} onCheckedChange={(checked) => table.toggleAllPageRowsSelected(!!checked) } > <Checkbox.Indicator /> </Checkbox.Root> ), cell: ({ row }) => ( <Checkbox.Root checked={row.getIsSelected()} onCheckedChange={(checked) => row.toggleSelected(!!checked)} > <Checkbox.Indicator /> </Checkbox.Root> ), }, { accessorKey: "email", header: ({ column }) => ( <Button variant="text" color="secondary" onClick={() => column.toggleSorting(column.getIsSorted() === "asc") } > Email <ArrowUpDown className="ml-1 size-4" /> </Button> ), }, { accessorKey: "amount", header: () => <div className="text-right">Amount</div>, cell: ({ row }) => { const formatted = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(row.getValue("amount")); return <div className="text-right font-medium">{formatted}</div>; }, }, ];

2. Create the Table Instance

import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel, } from "@tanstack/react-table"; const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), state: { sorting, columnFilters, rowSelection }, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, onRowSelectionChange: setRowSelection, });

3. Render with Table.*

Use flexRender from TanStack to render column definitions into chunks-ui styled cells:

import { flexRender } from "@tanstack/react-table"; import { Table } from "chunks-ui"; <Table.Root> <Table.Header> {table.getHeaderGroups().map((headerGroup) => ( <Table.Row key={headerGroup.id}> {headerGroup.headers.map((header) => ( <Table.Head key={header.id}> {flexRender(header.column.columnDef.header, header.getContext())} </Table.Head> ))} </Table.Row> ))} </Table.Header> <Table.Body> {table.getRowModel().rows.map((row) => ( <Table.Row key={row.id} data-state={row.getIsSelected() ? "selected" : undefined} > {row.getVisibleCells().map((cell) => ( <Table.Cell key={cell.id}> {flexRender(cell.column.columnDef.cell, cell.getContext())} </Table.Cell> ))} </Table.Row> ))} </Table.Body> </Table.Root>

4. Add Controls

Filter with Input, paginate with Button — no special wrapper needed:

<Input placeholder="Filter emails..." value={(table.getColumn("email")?.getFilterValue() as string) ?? ""} onChange={(e) => table.getColumn("email")?.setFilterValue(e.target.value)} /> <Button variant="outlined" onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()} > Previous </Button>

Why This Pattern?

  • No table logic in chunks-uiTable.* is pure styling, TanStack owns the state
  • Composable — swap TanStack for plain .map(), the styling stays the same
  • Familiar — same pattern as shadcn/ui, widely adopted in the React ecosystem
  • Type-safeColumnDef<T> gives you full type inference in cell renderers
Last updated on