Data table

A source-copy records workflow with controlled search, custom filters, sorting, page size, pagination and empty-result recovery.

BlocksUpdated October 8, 2026
Data & recordsdata-table

Browse records without adopting a data-grid frameworkLink to section

DataTable renders one typed record-browsing composition from consumer-owned columns and rows. It provides deterministic ready, loading, empty and error states, optional row actions, controlled query and sorting requests, page size, and page navigation.

The Block never fetches, filters, sorts, mutates, routes or interprets records. Those decisions remain in the consuming feature or data layer.

Source-copy Block

Copy the complete canonical directory into your application. There is no Blocks package, Registry installer, CLI, or automatic update channel for this Block today.

Canonical identityLink to section

ContractValue
CategoryData & records
Category slugdata
Block slugdata-table
Sourceapps/marketing/content/blocks/data/data-table/
Entry pointapps/marketing/content/blocks/data/data-table/index.tsx
ExportDataTable
Informational future Registry identitydata-data-table

The Registry identity is informational only. Registry installation is not available, and this Block does not create a manifest, generated JSON, route or delivery service.

Copy sourceLink to section

Configure the public UI package and tokens, then create src/components/blocks/data-table/ in your application. Copy all four files below, keeping their relative imports and client directives. In the Source tab, choose a file and use its copy button, or select the code manually when clipboard access is unavailable.

To retrieve all four files at once, open Code → Download ZIP for Data table in the Blocks catalog. Extract the archive and move its src/components/blocks/data-table/ folder into your application.

File structureLink to section

index.tsx
pagination.tsx
query-controls.tsx
types.ts
FileResponsibility
index.tsxTable composition, sorting controls, display states and public exports.
pagination.tsxPage navigation, page-size selection and pagination validation.
query-controls.tsxSearch input, filter slots, reset action and result summary.
types.tsTyped columns, rows, controllers and component props.

Import DataTable and its types from the directory's index.tsx entry point. The separate controls keep customization local; data operations remain yours.

Example records
RecordStateUpdatedRecord actions
NorthwindAvailableToday
ContosoPausedYesterday
Showing 1–2 of 6

The Preview tab renders the real canonical DataTable through its deterministic local fixture. It performs no data fetch or network request. The Source tab includes the complete canonical files at build time, not a duplicated implementation.

Your application copy does not receive automatic updates. Review later canonical changes deliberately and validate it in your application before adopting them. Return to the Blocks catalog to choose another pattern.

Install by copying sourceLink to section

  1. Copy the complete data-table directory to an application-owned path, such as src/components/blocks/data-table/.
  2. Install React 18 or newer and the public @pycolors/ui package.
  3. Load @pycolors/tokens/tokens.css and configure the semantic Tailwind utilities used by the Block.
  4. Import DataTable and its types from the copied local entry point.
records-table.tsx
"use client";

import { DataTable, type DataTableColumn } from "./blocks/data-table";

type RecordRow = Readonly<{
  id: string;
  label: string;
  state: "Available" | "Paused";
}>;

const columns = [
  { id: "label", header: "Record", cell: (row) => row.label, sortable: true },
  { id: "state", header: "State", cell: (row) => row.state, sortable: true },
] satisfies readonly DataTableColumn<RecordRow>[];

export function RecordsTable({ rows }: { rows: readonly RecordRow[] }) {
  return (
    <DataTable
      caption="Available records"
      columns={columns}
      emptyDescription="Add a record to begin."
      emptyTitle="No records"
      getRowId={(row) => row.id}
      rows={rows}
    />
  );
}

Consumer-owned contractLink to section

The consumer owns the row type, current rows, stable row IDs, unique column IDs, headers, cell rendering, state copy, row actions, search values, filters, matching rules, result counts, sort logic, pagination and page-size state. DataTable does not infer identifiers from visible text or array positions and does not interpret cell values.

The primary composition accepts caption, columns, rows, getRowId, emptyTitle, optional emptyDescription, emptyAction, renderRowActions, rowActionsLabel, pagination, pageSize, sorting, query, state and className. Provide at least one meaningful column and keep row/column IDs stable across rerenders.

Search, filter, sort and paginateLink to section

Pass the optional query controller to compose a labelled live-search input, consumer-owned filters, an optional reset action and result summary above the table. The Block forwards raw controlled values; matching, debouncing, request cancellation and page reset remain consumer responsibilities.

Showing 1–2 of 6 matching records.

Searchable sample records
UpdatedRecord actions
NorthwindAvailableToday
ContosoPausedYesterday

Local example only. No request is sent or data saved.

The demonstration searches a complete fictional collection before sorting and pagination. Reset changes local demo state only. Nothing is requested or saved. For remote records, use the server's matching total and ordering rather than filtering only a downloaded page.

Ready, loading, empty and error statesLink to section

Ready is the default. Loading replaces stale rows with the public loading presentation and suppresses pagination. Error presentation uses consumer-owned copy and recovery actions while rows and pagination stay hidden. A ready empty result is distinct from both loading and failure.

Pass emptyAction for a useful first-use or no-results next step. The action is rendered unchanged outside the table scroller; record creation, filter reset, permissions and focus after the empty state disappears remain consumer-owned.

New sample workspace
RecordStateUpdated
No records yet
Start with sample records to explore this table.

Interactive example only. No request is sent or data saved.

Filtered sample records
RecordStateUpdated
No matching records
No sample records match this filter.

Interactive example only. No request is sent or data saved.

empty-table-action.tsx
<DataTable
  {...tableProps}
  rows={filteredRows}
  emptyTitle="No matching records"
  emptyDescription="Clear your filters to see all available records."
  emptyAction={
    <button onClick={clearFilters} type="button">
      Clear filters
    </button>
  }
/>

Optional row actionsLink to section

renderRowActions receives the current row and may return consumer-owned buttons, links or menus. Give every repeated control a specific accessible name. The Block does not provide permissions, confirmation, routing, mutation or destructive-action behavior and never makes the entire row interactive.

Optional controlled paginationLink to section

Pagination is one-based. The Block renders valid page navigation from supplied page and totalPages, emits only valid page requests, and does not slice rows, fetch data, synchronize URLs or move focus. Those behaviors remain in the consumer.

controlled-record-pages.tsx
<DataTable
  {...tableProps}
  pagination={{
    navigationLabel: "Record pages",
    onPageChange: setPage,
    page,
    summary: `Page ${page} of ${totalPages}`,
    totalPages,
  }}
/>

Optional controlled page sizeLink to section

Pass pageSize with a selected positive safe integer, allowed options and an onPageSizeChange callback. The Block validates the presentation contract but does not slice rows or reset the current page for you.

Adjustable sample records
Updated
NorthwindAvailableToday
ContosoPausedYesterday

Showing 1–2 of 6. Local example only. No request is sent or data saved.

The example changes local size/page state and sorts a fresh copy before slicing. Nothing is requested from a server or persisted. For server pagination, keep approved sizes, true totals, cancellation and stale-result handling in your data layer.

Optional controlled sortingLink to section

Mark columns sortable: true and pass a sorting controller. Header buttons request ascending or descending state; they do not reorder the supplied rows. Your application must return rows that match the displayed sort state.

Sortable sample records
Updated
NorthwindAvailableToday
ContosoPausedYesterday
Page 1 of 3

Original sample order. Local example only. No request is sent or data saved.

The active sortable header receives aria-sort; direction glyphs are decorative. Tab, Enter and Space use native button behavior. Focus movement, comparator choice, locale, page reset and remote ordering remain consumer-owned.

Responsive and accessibility behaviorLink to section

  • Native table semantics remain intact at every viewport size.
  • The table scrolls horizontally inside its bounded wrapper when needed.
  • Query controls remain outside the table scroller and wrap on narrow layouts.
  • Pagination and page-size controls use native, labelled controls.
  • Loading, empty and error copy remain visible without relying only on color.
  • Consumer row actions must remain keyboard operable and clearly labelled.
  • The Block does not move focus after query, page, size or sort changes.

Focused semantic, interaction and representative axe tests verify these contracts, but they are not a complete accessibility certification. Review real long content, mobile widths, both themes, keyboard focus and assistive technology in the consuming application.

Deliberate exclusionsLink to section

This Block does not include row selection, bulk actions, a filtering engine, sorting engine, multi-column sorting, column visibility, resizing, reordering, virtualization, inline editing, export, saved views, data fetching, server pagination, caching, routing, permissions, mutations, authentication or billing.

It does not depend on TanStack Table, Next.js, Starter Free, Starter Pro, private/deep package imports or a new runtime library. Compose product-specific controls through query.filters or outside the Block and pass prepared rows.

Ownership, updates and rollbackLink to section

After copying, the consumer owns the source, integration, cell content, action behavior, data lifecycle and future maintenance. There is no automatic update or synchronization path. Compare future canonical revisions deliberately and adopt only changes that fit the application.

Rollback is removal or reversion of the copied data-table directory and its local integration. No package version, Registry state or remote Block state is involved.