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 — Column Settings

Table with a column visibility picker in the toolbar. Toggle columns on and off.

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

'use client';

import {useState} from 'react';
import {
  Table,
  useTableColumnSettings,
  useTableColumnSettingsState,
  proportional,
} from '@astryxdesign/core/Table';
import type {TableColumn} from '@astryxdesign/core/Table';
import {MultiSelector} from '@astryxdesign/core/MultiSelector';
import {VStack} from '@astryxdesign/core/Layout';
import {Toolbar} from '@astryxdesign/core/Toolbar';
import {Text} from '@astryxdesign/core/Text';

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

const users: User[] = [
  {
    id: '1',
    name: 'Alice Johnson',
    email: 'alice@example.com',
    role: 'Engineer',
    department: 'Platform',
    status: 'Active',
  },
  {
    id: '2',
    name: 'Bob Smith',
    email: 'bob@example.com',
    role: 'Designer',
    department: 'Product',
    status: 'Active',
  },
  {
    id: '3',
    name: 'Charlie Brown',
    email: 'charlie@example.com',
    role: 'Manager',
    department: 'Platform',
    status: 'Away',
  },
  {
    id: '4',
    name: 'Diana Prince',
    email: 'diana@example.com',
    role: 'Engineer',
    department: 'Infra',
    status: 'Active',
  },
  {
    id: '5',
    name: 'Eve Davis',
    email: 'eve@example.com',
    role: 'Admin',
    department: 'Operations',
    status: 'Inactive',
  },
];

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

const columnOptions = [
  {key: 'name' as const, label: 'Name', isAlwaysVisible: true},
  {key: 'email' as const, label: 'Email'},
  {key: 'role' as const, label: 'Role'},
  {key: 'department' as const, label: 'Department'},
  {key: 'status' as const, label: 'Status'},
];

const allKeys: string[] = ['name', 'email', 'role', 'department', 'status'];

export default function TableColumnSettingsTable() {
  const [activeKeys, setActiveKeys] = useState<string[]>(allKeys);

  const state = useTableColumnSettingsState({
    columns: columnOptions,
    activeColumnKeys: activeKeys,
    onChangeActiveColumnKeys: keys => setActiveKeys([...keys]),
  });

  const plugin = useTableColumnSettings<User>(state.columnSettingsConfig);

  const selectorOptions = columnOptions.map(c => ({
    value: c.key,
    label: c.label,
    disabled: c.isAlwaysVisible === true,
  }));

  return (
    <VStack gap={3} width="100%">
      <Toolbar
        label="Table actions"
        startContent={<Text type="label">Team</Text>}
        endContent={
          <MultiSelector
            label="Columns"
            isLabelHidden
            options={selectorOptions}
            value={[...state.activeColumnKeys]}
            onChange={state.setActiveColumnKeys}
          />
        }
      />
      <Table
        data={users}
        columns={allColumns}
        idKey="id"
        hasHover
        plugins={{columnSettings: plugin}}
      />
    </VStack>
  );
}

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.