Agents (llms.txt)

Data Table

Sortable data table powered by TanStack Table and the Foundations Table primitive

May 9
M
Mercury Working Capital
-$2,200.00Ops / PayrollWorking Capital Loan
May 9
O
Office Stop Co.
-$662.70Credit accountJane B. ··0330
May 9
O
Office Stop Co.
$563.94Credit accountJane B. ··0330
May 9
M
Milgram Brokerage
$2,760.75Ops / PayrollJane B. ··3745
May 9
L
Lily's Eatery
$25.89Credit accountJane B. ··5555
import type { ColumnDef } from '@tanstack/react-table';

import { Avatar } from '@/components/avatar';
import { DataTable } from '@/components/data-table';
import { Surface } from '@/components/surface';

type Txn = {
  id: string;
  date: string;
  payee: string;
  amount: number;
  account: string;
  method: string;
};

const data: Txn[] = [
  {
    id: '1',
    date: 'May 9',
    payee: 'Mercury Working Capital',
    amount: -2200,
    account: 'Ops / Payroll',
    method: 'Working Capital Loan',
  },
  {
    id: '2',
    date: 'May 9',
    payee: 'Office Stop Co.',
    amount: -662.7,
    account: 'Credit account',
    method: 'Jane B. ··0330',
  },
  {
    id: '3',
    date: 'May 9',
    payee: 'Office Stop Co.',
    amount: 563.94,
    account: 'Credit account',
    method: 'Jane B. ··0330',
  },
  {
    id: '4',
    date: 'May 9',
    payee: 'Milgram Brokerage',
    amount: 2760.75,
    account: 'Ops / Payroll',
    method: 'Jane B. ··3745',
  },
  {
    id: '5',
    date: 'May 9',
    payee: "Lily's Eatery",
    amount: 25.89,
    account: 'Credit account',
    method: 'Jane B. ··5555',
  },
];

const fmt = (n: number) =>
  new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD',
  }).format(n);

const columns: ColumnDef<Txn>[] = [
  { accessorKey: 'date', header: 'Date' },
  {
    accessorKey: 'payee',
    header: 'To/From',
    cell: ({ row }) => (
      <div className="flex items-center gap-2">
        <Avatar size="sm">
          <Avatar.Fallback>{row.original.payee.charAt(0)}</Avatar.Fallback>
        </Avatar>
        <span>{row.original.payee}</span>
      </div>
    ),
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: { align: 'end' },
    cell: ({ getValue }) => {
      const v = getValue<number>();
      return (
        <span
          className={
            v < 0 ? 'text-foreground' : 'font-medium text-success tabular-nums'
          }
        >
          {fmt(v)}
        </span>
      );
    },
  },
  { accessorKey: 'account', header: 'Account' },
  { accessorKey: 'method', header: 'Method' },
];

export default function DataTablePreview() {
  return (
    <Surface elevation="default" padding="none" className="w-full">
      <DataTable columns={columns} data={data} onRowClick={() => {}} />
    </Surface>
  );
}

Dependencies

Source Code

'use client';

import {
  type ColumnDef,
  flexRender,
  getCoreRowModel,
  getSortedRowModel,
  type SortingState,
  useReactTable,
} from '@tanstack/react-table';
import { useState } from 'react';

import { Table } from '@/components/table';
import { cn } from '@/lib/utils/classnames';

interface DataTableProps<TData, TValue>
  extends Omit<React.ComponentPropsWithRef<'table'>, 'children'> {
  columns: ColumnDef<TData, TValue>[];
  data: TData[];
  density?: 'sm' | 'md';
  /** Sticky header — only useful when the table is inside a scroll container. */
  stickyHeader?: boolean;
  /** Called when a body row is clicked. Receives the original row data. */
  onRowClick?: (row: TData) => void;
  /** Empty-state placeholder when `data.length === 0`. */
  emptyState?: React.ReactNode;
}

const DataTable = <TData, TValue>({
  ref,
  columns,
  data,
  density = 'md',
  stickyHeader,
  onRowClick,
  emptyState,
  className,
  ...rest
}: DataTableProps<TData, TValue>) => {
  const [sorting, setSorting] = useState<SortingState>([]);

  const table = useReactTable({
    data,
    columns,
    state: { sorting },
    onSortingChange: setSorting,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
  });

  return (
    <Table ref={ref} density={density} className={className} {...rest}>
      <Table.Header sticky={stickyHeader}>
        {table.getHeaderGroups().map((headerGroup) => (
          <Table.Row key={headerGroup.id}>
            {headerGroup.headers.map((header) => {
              const canSort = header.column.getCanSort();
              const sortDir = header.column.getIsSorted();
              const align = (
                header.column.columnDef.meta as
                  | { align?: 'start' | 'center' | 'end' }
                  | undefined
              )?.align;

              if (canSort) {
                const toggle = header.column.getToggleSortingHandler();
                return (
                  <Table.SortableHead
                    key={header.id}
                    align={align}
                    sort={sortDir === false ? false : sortDir}
                    onSort={(event) => toggle?.(event)}
                  >
                    {header.isPlaceholder
                      ? null
                      : flexRender(
                          header.column.columnDef.header,
                          header.getContext()
                        )}
                  </Table.SortableHead>
                );
              }

              return (
                <Table.Head key={header.id} align={align}>
                  {header.isPlaceholder
                    ? null
                    : flexRender(
                        header.column.columnDef.header,
                        header.getContext()
                      )}
                </Table.Head>
              );
            })}
          </Table.Row>
        ))}
      </Table.Header>

      <Table.Body>
        {table.getRowModel().rows.length === 0 ? (
          <Table.Row>
            <Table.Cell
              colSpan={columns.length}
              className="py-12 text-center text-foreground-secondary"
            >
              {emptyState ?? 'No results.'}
            </Table.Cell>
          </Table.Row>
        ) : (
          table.getRowModel().rows.map((row) => (
            <Table.Row
              key={row.id}
              onClick={onRowClick ? () => onRowClick(row.original) : undefined}
              className={cn(onRowClick && 'cursor-pointer')}
            >
              {row.getVisibleCells().map((cell) => {
                const align = (
                  cell.column.columnDef.meta as
                    | { align?: 'start' | 'center' | 'end' }
                    | undefined
                )?.align;
                return (
                  <Table.Cell key={cell.id} align={align}>
                    {flexRender(cell.column.columnDef.cell, cell.getContext())}
                  </Table.Cell>
                );
              })}
            </Table.Row>
          ))
        )}
      </Table.Body>
    </Table>
  );
};

export type { DataTableProps };
export { DataTable };

DataTable is a thin integration of TanStack Table on top of the Foundations Table primitive. Pass columns and data and you get sorting, alignment, sticky headers, click-through rows, and a built-in empty state — without giving up Foundations’ table styling.

Anatomy


          const columns: ColumnDef<Row>[] = [
  { accessorKey: 'name', header: 'Name' },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: { align: 'end' },
    cell: ({ getValue }) => formatMoney(getValue<number>()),
  },
];

<DataTable columns={columns} data={rows} onRowClick={...} />
        

API Reference

Prop Default Type
columns - ColumnDef[]
data - TData[]
density "md" "sm""md"
stickyHeader - boolean
onRowClick - (row) => void
emptyState - ReactNode

Per-column alignment is set via columnDef.meta.align: 'start' | 'center' | 'end'.

Examples

Default

May 9
M
Mercury Working Capital
-$2,200.00Ops / PayrollWorking Capital Loan
May 9
O
Office Stop Co.
-$662.70Credit accountJane B. ··0330
May 9
O
Office Stop Co.
$563.94Credit accountJane B. ··0330
May 9
M
Milgram Brokerage
$2,760.75Ops / PayrollJane B. ··3745
May 9
L
Lily's Eatery
$25.89Credit accountJane B. ··5555
import type { ColumnDef } from '@tanstack/react-table';

import { Avatar } from '@/components/avatar';
import { DataTable } from '@/components/data-table';
import { Surface } from '@/components/surface';

type Txn = {
  id: string;
  date: string;
  payee: string;
  amount: number;
  account: string;
  method: string;
};

const data: Txn[] = [
  {
    id: '1',
    date: 'May 9',
    payee: 'Mercury Working Capital',
    amount: -2200,
    account: 'Ops / Payroll',
    method: 'Working Capital Loan',
  },
  {
    id: '2',
    date: 'May 9',
    payee: 'Office Stop Co.',
    amount: -662.7,
    account: 'Credit account',
    method: 'Jane B. ··0330',
  },
  {
    id: '3',
    date: 'May 9',
    payee: 'Office Stop Co.',
    amount: 563.94,
    account: 'Credit account',
    method: 'Jane B. ··0330',
  },
  {
    id: '4',
    date: 'May 9',
    payee: 'Milgram Brokerage',
    amount: 2760.75,
    account: 'Ops / Payroll',
    method: 'Jane B. ··3745',
  },
  {
    id: '5',
    date: 'May 9',
    payee: "Lily's Eatery",
    amount: 25.89,
    account: 'Credit account',
    method: 'Jane B. ··5555',
  },
];

const fmt = (n: number) =>
  new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD',
  }).format(n);

const columns: ColumnDef<Txn>[] = [
  { accessorKey: 'date', header: 'Date' },
  {
    accessorKey: 'payee',
    header: 'To/From',
    cell: ({ row }) => (
      <div className="flex items-center gap-2">
        <Avatar size="sm">
          <Avatar.Fallback>{row.original.payee.charAt(0)}</Avatar.Fallback>
        </Avatar>
        <span>{row.original.payee}</span>
      </div>
    ),
  },
  {
    accessorKey: 'amount',
    header: 'Amount',
    meta: { align: 'end' },
    cell: ({ getValue }) => {
      const v = getValue<number>();
      return (
        <span
          className={
            v < 0 ? 'text-foreground' : 'font-medium text-success tabular-nums'
          }
        >
          {fmt(v)}
        </span>
      );
    },
  },
  { accessorKey: 'account', header: 'Account' },
  { accessorKey: 'method', header: 'Method' },
];

export default function DataTablePreview() {
  return (
    <Surface elevation="default" padding="none" className="w-full">
      <DataTable columns={columns} data={data} onRowClick={() => {}} />
    </Surface>
  );
}

Previous

Container

Next

Date Picker