Data Table

hardUI Component

Data tables are everywhere and deceptively deep: a column model, sorting/filtering/pagination, scaling to large datasets, and proper table semantics. The pivotal decision is where the work happens — the client or the server. We'll use the RADIO framework.

R

Requirements

The single most important question is how much data there is — it decides whether sort/filter/paginate happen on the client or the server.

Before designing, what should you nail down?

Functional vs non-functional

Functional: render rows/columns, sort, filter, paginate, maybe select/edit. Non-functional: stays responsive at the required data scale, uses semantic table markup with sort state announced, and the layout works on small screens.

A

Architecture

Drive everything from a column model (data describing each column). A hook holds sort/filter/page state and produces the visible rows; the table, header, and rows are presentational.

Click a node to see what it owns.

<DataTable columns data>

Does: Composition root; renders from the column model.

State: none (delegates to useTable)

useTable()

Does: Owns sort/filter/page state; computes the visible rows (or requests them from the server).

State: sort, filters, page, pageSize, selection

<TableHeader>

Does: Sortable column headers with aria-sort.

<TableBody>

Does: Renders visible rows (windowed for large data).

<Row> / <Cell>

Does: Presentational; cell content via the column accessor.

<Pagination>

Does: Page controls / 'load more'.

Where do sort / filter / paginate happen?

Common default

Pick when: Large datasets (tens of thousands+). The server sorts/filters/pages; the client renders a page.

PROS
  • Scales to millions of rows
  • Small payloads; the browser never holds the full set
CONS
  • Network round-trip per change (debounce filters)
  • More API surface
D

Data Model

The column model makes the table generic: each column says how to render and sort itself, so the table component never hard-codes fields.

Column model + table state.

Loading editor…

Hand-roll the table logic or use a headless library?

Common default

Pick when: Non-trivial tables — sorting, filtering, grouping, virtualization, column sizing without reinventing them.

PROS
  • Feature-complete + headless (you own the markup)
  • Handles edge cases
CONS
  • API to learn
  • A dependency
I

Interface

Take columns + data and emit change events; controlled state lets the parent drive server-side operations.

Component contract.

Loading editor…
O

Optimizations & Deep Dives

The concerns that matter: scaling (client vs server + virtualization), accessibility, and rendering performance.

The signature insight: scale decides the architecture

For a few hundred rows, do everything on the client — instant and simple (the demo). For large datasets, the browser can't download or sort millions of rows: push sort/filter/pagination to the server and virtualize the rendered rows so the DOM stays small. Picking client-side for big data (or server-side for tiny data) is the classic mistake — match the approach to the row count.

Sortable, filterable, paginated table (editable)

All client-side here. Click a header to sort (note aria-sort), filter by name/role, and page through. Built on a semantic <table>.

Loading editor…

The rest:

  • Virtualization: for long tables, render only visible rows (windowing). Watch out for sticky headers and accessible row semantics when rows are absolutely positioned.
  • Accessibility: use real <table>/<th scope>/<td>, set aria-sort on the active column header, add a <caption>, and keep sort controls keyboard-operable. Don't rebuild a table out of <div>s and lose semantics.
  • Rendering performance: memoise rows and cells; key by a stable row id; avoid recreating the column model each render.
  • Filtering: debounce text filters; if server-side, cancel/ignore stale responses (latest-wins).
  • Responsive: horizontal scroll with a sticky first column, or stack cells into cards on narrow screens.

Self-check: a production-ready table covers…

0 / 8 covered

Going deeper (tap to reveal)