Experience System
Components

Table

Headless HTML table for tabular data on TanStack Table, with design-token spacing and class hooks for headers, rows, and cells.

IDNameRoleEmail
1001Maya AdamsPlannermaya.adams@by.com
1002Noah PatelDispatchernoah.patel@by.com
1003Lina ChenAnalystlina.chen@by.com
1004Diego CruzManagerdiego.cruz@by.com

Table from @by/table renders a semantic <table> with thead and tbody. You supply data and TanStack columns; density, alignment, and borders are entirely class-driven (className, headerClassName, bodyClassName, rowClassName, cellClassName). The root applies text-base tabular-nums by default so numeric columns align cleanly unless you override it.

Installation

The component is published from @by/table. Add the package with your package manager:

pnpm add @by/table

In this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/table. Theme tokens and Tailwind utilities such as px-scaled-* assume ThemeProvider from @by/experience-system in the app shell.

Composition

Use the following composition to render a Table:

Table (native table)
├── thead
│   └── tr
│       └── th (one per header; scope col / colgroup)
└── tbody
    └── tr (one per row)
        └── td (one per visible cell)

Table owns the table shell; you define ColumnDef values (often with createColumnHelper) and pass data. Advanced behavior (sorting, filtering, pagination) is optional via the options prop and TanStack row models. See TanStack Table.

Usage

Keep data / columns stable in a small wrapper, and pass layout classes from the parent so designers can tune spacing without touching column definitions.

import { createColumnHelper, Table, type TableProps } from '@by/table';

type Row = { id: number; name: string };

const columnHelper = createColumnHelper<Row>();
const columns = [
  columnHelper.accessor('id', { header: 'ID', cell: (info) => info.getValue() }),
  columnHelper.accessor('name', { header: 'Name', cell: (info) => info.getValue() }),
];

const data: Row[] = [
  { id: 1, name: 'Ada' },
  { id: 2, name: 'Lin' },
];

type TableViewProps = Omit<TableProps<Row>, 'data' | 'columns' | 'options'>;

export function UserTable(props: TableViewProps) {
  return <Table data={data} columns={columns} {...props} />;
}

// Caller
<UserTable
  className="w-[760px] border-collapse"
  headerClassName="[&_tr_th]:px-scaled-3 [&_tr_th]:py-scaled-2"
  bodyClassName="divide-y divide-neutral-alpha-5"
  cellClassName="px-scaled-3 py-scaled-2 text-start"
/>

Table is a client component ('use client' in the package). Use it inside a Client Component or a dynamic import when using the Next.js App Router.

When to use

Use Table when you need real table semantics and keyboard-friendly reading order, with TanStack’s column model and full control over styling via classes.

When not to use

For non-tabular layouts, use Grid. For loading placeholders, use Skeleton. For very large datasets, add virtualization around rows (for example @tanstack/react-virtual)—not built into Table today.

Examples

Overview

Four-column sample data with border-collapse, row dividers on the body, px-scaled-3 / py-scaled-2 on th via headerClassName, and the same padding on cells. This mirrors TableOverview plus the Table.stories.tsx default args.

IDNameRoleEmail
1001Maya AdamsPlannermaya.adams@by.com
1002Noah PatelDispatchernoah.patel@by.com
1003Lina ChenAnalystlina.chen@by.com
1004Diego CruzManagerdiego.cruz@by.com

Styled rows

Two-column shipment list inside a rounded border: zebra rowClassName, column-specific alignment with nth-child selectors on the table, and compact px-scaled-1 padding. Same markup as TableStyled.

Load IDStatus
LD-4191On Time
LD-4192At Risk
LD-4193Delayed
LD-4194On Time

API Reference

Table is implemented in this repo on top of TanStack Table. It forwards native HTML table attributes except where TableProps reserves data for the row array. The package re-exports createColumnHelper, flexRender, common row models, and related types from @tanstack/react-table—see TanStack’s docs for those.

Table

PropTypeDefault
dataTData[](required)
columnsColumnDef<TData, any>[](required)
optionsPartial of TanStack TableOptions excluding data, columns, and getCoreRowModelundefined
classNamestringundefined
headerClassNamestringundefined
bodyClassNamestringundefined
rowClassNamestring | ((row: TData, index: number) => string)undefined
cellClassNamestringundefined

The root table merges text-base tabular-nums with your className. thead merges border-b border-neutral-6 with headerClassName; tbody merges divide-y divide-neutral-alpha-5 with bodyClassName. Each th includes font-medium text-left in addition to your header/cell class strategy.

options is merged into the internal useReactTable call with getCoreRowModel always set. Use it for sorting, filtering, pagination, and other table options.

Accessibility

Output is a semantic <table> with <thead> and <tbody>. Header cells use scope="col" (or colgroup when sub-headers exist). Add a caption, visible title, or aria-label when the purpose is not obvious. For interactive headers (sort, filter), supply aria-sort, tabIndex, and keyboard behavior in your column renderers. See MDN: Table accessibility and WAI-ARIA table pattern.

Keyboard interactions

KeyDescription
Tab / Shift+TabMoves focus through focusable elements inside cells in DOM order.
Arrow keysNot handled by Table itself; implement in cell content when building grid or spreadsheet-style interaction.

Source in the repo: packages/table/src/components/Table.tsx. Agent-oriented contracts: packages/table/src/components/Table.instructions.md.