Codeways UI
Browse components

Data display

DataTable

Sorting, selection, pagination and toolbar, built on TanStack Table.

@codeways/ui/data-table

Sorting, selection, search and pagination

Client-side by default - sort/filter/page state lives internally unless `manual*` props are passed. Search, click a sortable header, or select rows.

Invoices
INV-000181Lanka Tea ExportsAmara SilvaAUD1,850.00Paid
INV-000182Globex DigitalNadeesha PereraUSD4,200.00Paid
INV-000183Colombo Freight CoRuwan JayasuriyaLKR612,500.00Overdue by 4 days
INV-000184Sunrise ApparelAmara SilvaAUD980.00Partially paid
INV-000185Acme Pty LtdDilani FernandoUSD3,100.00Draft
Rows per page
1–5 of 12

Loading

Skeleton rows, shape-matched to the visible columns - never a spinner.

Invoices loading

Empty

First-use empty state when there is no data, a filtered variant when `isFiltered` is set.

No invoices

No invoices yet

Invoices you send to clients will show up here.

Usage

import { DataTable, type DataTableEmptyOptions } from "@codeways/ui/data-table";
import { Money } from "@codeways/ui/money";
import type { ColumnDef } from "@tanstack/react-table";

const columns: ColumnDef<Invoice, unknown>[] = [
  { accessorKey: "number", header: "Invoice", enableSorting: true },
  {
    accessorKey: "amount",
    header: "Amount",
    enableSorting: true,
    cell: ({ row }) => <Money amount={row.original.amount} currency={row.original.currency} />,
    meta: { align: "right" },
  },
];

<DataTable
  columns={columns}
  data={invoices}
  getRowId={(row) => row.id}
  enableRowSelection
  bulkActions={(selected) => <Button onClick={() => send(selected)}>Send</Button>}
  search={q}
  onSearchChange={(value) => setParam({ q: value || null })}
  empty={{ icon: FileText, title: "No invoices yet", action: <Button variant="primary">New invoice</Button> }}
  isFiltered={Boolean(q)}
/>