EXEPERTAI LAB

Research alpha

Time Machine
EXEPERTAI LAB
GALLERY / COLLECTION
← Browse Astryx gallery
Table·component·@astryxdesign/core

Table

Styled, data-driven table with density, dividers, hover highlight, striped rows, and named plugin support. T must extend Record<string, unknown>.

Open in Playground @astryxdesign/core/Table

Showcases and examples

12 documented examples

Table — Paginated Data

Paginated data table navigating through a larger dataset page by page.

Preview loads on approachPreview loads on approach
Exact source · table-paginated-table
// Copyright (c) Meta Platforms, Inc. and affiliates.

'use client';

import {useState} from 'react';
import {
  Table,
  useTablePagination,
  paginateData,
  proportional,
} from '@astryxdesign/core/Table';
import type {TableColumn} from '@astryxdesign/core/Table';
import {Section} from '@astryxdesign/core/Section';

interface User extends Record<string, unknown> {
  id: string;
  name: string;
  email: string;
  role: string;
}

const names = [
  'Alice Johnson',
  'Bob Smith',
  'Charlie Brown',
  'Diana Prince',
  'Eve Davis',
  'Frank Miller',
  'Grace Lee',
  'Hank Wilson',
  'Ivy Chen',
  'Jack Turner',
  'Karen White',
  'Leo Garcia',
  'Mia Thompson',
  'Noah Martinez',
  'Olivia Clark',
  'Paul Harris',
  'Quinn Walker',
  'Rachel Adams',
  'Sam Robinson',
  'Tina Scott',
];

const roles = ['Engineer', 'Designer', 'Manager', 'Admin', 'Analyst'];

const users: User[] = names.map((name, i) => ({
  id: String(i + 1),
  name,
  email: `${name.split(' ')[0].toLowerCase()}@example.com`,
  role: roles[i % roles.length],
}));

const columns: TableColumn<User>[] = [
  {key: 'name', header: 'Name', width: proportional(1)},
  {key: 'email', header: 'Email', width: proportional(2)},
  {key: 'role', header: 'Role', width: proportional(1)},
];

export default function TablePaginatedTable() {
  const [page, setPage] = useState(1);
  const pageSize = 5;

  const plugin = useTablePagination<User>({
    page,
    onPageChange: setPage,
    totalItems: users.length,
    pageSize,
  });

  return (
    <Section>
      <Table
        data={paginateData(users, page, pageSize)}
        columns={columns}
        idKey="id"
        plugins={{pagination: plugin}}
      />
    </Section>
  );
}

Usage

Table displays structured data in rows and columns with consistent dimensionality. It supports rich cell content, sorting, selection, pagination, and column management through a composable plugin system. Use Table for data sets with uniform structure; for simpler or inconsistent data, consider a list or card layout instead.

  • Use density and divider variants to match the information density and scanning needs of your data.
  • Compose rich cell content with Astryx components like Badge, StatusDot, and Avatar via renderCell.
  • In children mode, put every row inside TableHeader, TableBody, or TableFooter. <table> cannot contain a <tr> directly: the HTML parser inserts an implied <tbody> for server-rendered markup and React does not on the client, so unwrapped rows mismatch on hydration.
  • Set explicit width on every column using proportional() or pixel(). proportional(1) gives equal flex distribution with a 120px minimum that prevents columns from collapsing on narrow viewports. Omitting width skips the minimum.
  • Use the data-driven API from React Server Components: proportional(), pixel(), and column definitions without function props are server-safe. Columns using renderCell (or any function prop) need the table wrapped in a "use client" component, since functions cannot cross the server-client boundary.
  • Use a table for data without consistent columns. Use a list or card layout for heterogeneous content.
  • Enable every plugin at once. Add only the features your use case requires to keep the interface focused.
  • Omit width on text-heavy columns; without an explicit proportional() width they have no minimum and can squish to near-zero on mobile.

Typed props

PropType and behavior
dataT[]
Array of data items to render as rows. T must extend Record<string, unknown> (use interface MyRow extends Record<string, unknown> for custom types).
columnsTableColumn<T>[]
Column definitions: each column has {key, header, width?, align?, renderCell?}. The header field sets the column heading text. If omitted, columns are auto-generated from data object keys. The width field is typed as ColumnWidth (not a number); use proportional(n) or pixel(n) helpers imported from @astryxdesign/core/Table. Example: width: pixel(120) for 120px fixed, width: proportional(1) for flex distribution.
idKey(keyof T & string) | ((item: T) => string | number)
Row key for React reconciliation. Pass a property name string or a function. Falls back to row index if omitted.
density'compact' | 'balanced' | 'spacious' · default 'balanced'
Row density controlling cell padding and font size.
dividers'rows' | 'columns' | 'grid' | 'none' · default 'rows'
Divider style rendered between cells.
isStripedboolean · default false
Applies a background wash to even-numbered rows.
hasHoverboolean · default false
Applies a hover highlight background to rows on pointer devices.
verticalAlign'middle' | 'top' | 'bottom' · default 'middle'
Vertical alignment for body row cells. Controls vertical-align on the <td> elements.
textOverflow'wrap' | 'truncate' · default 'wrap'
How body cell text behaves when it exceeds the column width. 'wrap' lets text wrap and the row grow taller; 'truncate' clips with an ellipsis (default-rendered cells show a tooltip on hover when truncated). Header cells always truncate.
pluginsRecord<string, TablePlugin<T>>
Named plugins that extend table behavior via the transform pipeline. Converted to an ordered array internally.
rowIndexStartnumber · default 1
ARIA row index (1-based) for the first rendered body row. The row ordinal is an accessibility concern independent of any visible index column, so setting this (or rowCount) makes the table emit aria-rowindex on body rows and aria-rowcount on the table. For a paginated/windowed view, pass the offset of the first visible row (e.g. (page - 1) * pageSize + 1) so aria-rowindex reflects position in the full dataset. Data-driven mode only.
rowCountnumber
Total number of body rows across all pages/windows, used for aria-rowcount so assistive tech can announce "row X of Y" against the full dataset. When omitted but rowIndexStart is set (windowed view with an unknown total), aria-rowcount is set to -1 per the ARIA unknown-count convention. Data-driven mode only.
childrenReactNode
Children mode: compose the table yourself from TableHeader / TableBody / TableFooter, each holding TableRow and TableCell, instead of using data-driven rendering. The children are passed straight to the <table>, so the section is yours to supply. A TableRow placed directly in Table emits <table><tr>, which is invalid HTML and mismatches on hydration (the parser inserts an implied <tbody> for server-rendered markup; React does not on the client). Data-driven mode renders the sections for you.
xstyleStyleXStyles
StyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value: not an inline style object like style={{}}.

Anatomy

Table · required

Semantic table element that groups the table sections, rows, and cells.

Scroll region · required

Outer region that scrolls horizontally, enters the keyboard order, and contains overscroll only while the columns overflow.

Header section · optional

Column-heading section generated when data-driven columns are present or supplied with TableHeader in children mode.

Column header cell · optional

Cell that identifies one column and may contain sorting or bulk-selection controls.

Sort control · optional

Button that wraps a sortable column label and changes that column's sort direction.

Sort indicator glyph · optional

Directional symbol rendered by Icon inside a Sort control.

Sort priority · optional

Number shown for a sorted column when multi-column sorting is active.

Selection control · optional

CheckboxInput rendered in the header and selectable body rows by the selection plugin.

Body section · required

Section containing data rows or the current empty state; data-driven mode renders it automatically.

Row · optional

Repeated TableRow that groups cells in a standard header, body, or footer row.

Cell · optional

TableCell containing one value or caller-provided content in a standard body or footer row.

Default empty state · optional

Compact EmptyState shown for an empty data array unless it is replaced or disabled.

Expansion control · optional

Button in a leading cell that expands or collapses one expandable row.

Expansion glyph · optional

Directional symbol rendered by Icon inside an Expansion control.

Expanded detail panel · optional

Detail row and spanning cell rendered below an expanded row around caller-provided content.

Footer section · optional

Optional summary or totals section supplied with TableFooter in children mode.

Theming

Targets

astryx-table
astryx-table-scroll-wrapper
astryx-table-header
astryx-table-body
astryx-table-footer
astryx-table-row
astryx-table-cell

Visual props: density

astryx-table-header-cell

Visual props: density

astryx-base-table

Deprecated; use table.