JGuang

数据表格

Table · Astryx

交互预览
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0

数据表格 · Table

展示结构化数据,示例涵盖排序、筛选、选择、分组和分页。

安装与前置条件

npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0

需要 React 19+,按顺序导入基础 CSS 和主题,并设置 Theme Provider。参见开始使用。

示例与使用方式

  • 数据表格 · 综合展示 1:首先体验这个示例,再查看代码理解组件的组合方式。
  • 数据表格 · 交互示例 2:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 粘性布局 3:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 交互示例 4:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 交互示例 5:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 筛选 6:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 浮动 7:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 交互示例 8:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 分组 9:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 交互示例 10:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 筛选 11:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 交互示例 12:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 交互示例 13:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 交互示例 14:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 交互示例 15:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 交互示例 16:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 状态 17:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 交互示例 18:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 交互示例 19:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 交互示例 20:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
  • 数据表格 · 交互示例 21:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。

API 与上游详细参考

优先使用 0.7.1 官方中文说明;上游尚未提供中文的条目保留英文。属性名、类型及默认值保持原生 API,不把示例状态名称当成组件属性。

Table

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.

属性类型默认值说明
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. Omitting width keeps an equal flexible share with a compact 60px readability floor. To give one column a different floor, set width: proportional(1, {minWidth: 80}); its minWidth replaces the 60px floor for that column. Example: width: pixel(120) for 120px fixed, width: proportional(1) for a flexible column with a 120px default minimum.
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''balanced'Row density controlling cell padding and font size.
dividers'rows' | 'columns' | 'grid' | 'none''rows'Divider style rendered between cells.
isStripedbooleanfalseApplies a background wash to even-numbered rows.
hasHoverbooleanfalseApplies a hover highlight background to rows on pointer devices.
verticalAlign'middle' | 'top' | 'bottom''middle'Vertical alignment for body row cells. Controls vertical-align on the <td> elements.
textOverflow'wrap' | 'truncate''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.
rowIndexStartnumber1ARIA 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.
rowCountnumberTotal 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.
childrenReactNodeChildren 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.
xstyleStyleXStylesStyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value: not an inline style object like style={{}}.

使用建议

  • 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.
  • 避免: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.

主题 API

{
  "targets": [
    {
      "className": "astryx-table"
    },
    {
      "className": "astryx-table-scroll-wrapper"
    },
    {
      "className": "astryx-table-header"
    },
    {
      "className": "astryx-table-body"
    },
    {
      "className": "astryx-table-footer"
    },
    {
      "className": "astryx-table-row"
    },
    {
      "className": "astryx-table-cell",
      "visualProps": [
        "density"
      ]
    },
    {
      "className": "astryx-table-header-cell",
      "visualProps": [
        "density"
      ]
    }
  ]
}

TableHeader

TableBody

TableFooter

TableRow

TableCell

TableHeaderCell

useTableSelection

useTableSelectionState

TableSelectionToolbar

useTableSortable

useTableTreeData

useTableTreeState

useTablePagination

useTableColumnSettings

useTableFiltering

useTableFilterState

组件结构

  • Table: Semantic table element that groups the table sections, rows, and cells.
  • Scroll region: Outer region that scrolls horizontally, enters the keyboard order, and contains overscroll only while the columns overflow.
  • Header section: Column-heading section generated when data-driven columns are present or supplied with TableHeader in children mode.
  • Column header cell: Cell that identifies one column and may contain sorting or bulk-selection controls.
  • Sort control: Button that wraps a sortable column label and changes that column's sort direction.
  • Sort indicator glyph: Directional symbol rendered by Icon inside a Sort control.
  • Sort priority: Number shown for a sorted column when multi-column sorting is active.
  • Selection control: CheckboxInput rendered in the header and selectable body rows by the selection plugin.
  • Body section: Section containing data rows or the current empty state; data-driven mode renders it automatically.
  • Row: Repeated TableRow that groups cells in a standard header, body, or footer row.
  • Cell: TableCell containing one value or caller-provided content in a standard body or footer row.
  • Default empty state: Compact EmptyState shown for an empty data array unless it is replaced or disabled.
  • Expansion control: Button in a leading cell that expands or collapses one expandable row.
  • Expansion glyph: Directional symbol rendered by Icon inside an Expansion control.
  • Expanded detail panel: Detail row and spanning cell rendered below an expanded row around caller-provided content.
  • Footer section: Optional summary or totals section supplied with TableFooter in children mode.

Table Body

<tbody> wrapper for children mode. Holds the data rows. A row must sit inside a section: <table> cannot contain a <tr> directly, because the HTML parser inserts an implied <tbody> when it parses server-rendered markup and React does not when it renders on the client, so rows written straight into Table mismatch on hydration. The data-driven data={...} mode renders this element itself; in children mode it is yours to supply.
属性类型默认值说明
children *ReactNodeThe <tbody> rows.
xstyleStyleXStylesStyleX styles for layout customization. Must be a stylex.create() value: not an inline style object like style={{}}.

主题 API

{
  "targets": [
    {
      "className": "astryx-table-body"
    }
  ]
}

Table Cell

<td> 包装器,读取 TableContext 以在 Table 内部使用时应用密度内边距、字体大小和分隔线边框。
属性类型默认值说明
childrenReactNode单元格内容。
<tfoot> wrapper for children mode. Holds summary or total rows beneath the body. A row must sit inside a section: <table> cannot contain a <tr> directly, because the HTML parser inserts an implied <tbody> when it parses server-rendered markup and React does not when it renders on the client, so the two trees mismatch on hydration. Children mode only: the data-driven data={...} path renders a header and a body, never a footer.
属性类型默认值说明
children *ReactNodeThe <tfoot> rows.
xstyleStyleXStylesStyleX styles for layout customization. Must be a stylex.create() value: not an inline style object like style={{}}.

主题 API

{
  "targets": [
    {
      "className": "astryx-table-footer"
    }
  ]
}

Table Header

<thead> wrapper for children mode. Holds the header row, whose cells are TableHeaderCell. A row must sit inside a section: <table> cannot contain a <tr> directly, because the HTML parser inserts an implied <tbody> when it parses server-rendered markup and React does not when it renders on the client, so the two trees mismatch on hydration. The data-driven data={...} mode renders this element itself whenever columns are supplied; in children mode it is yours to supply.
属性类型默认值说明
children *ReactNodeThe <thead> rows.
xstyleStyleXStylesStyleX styles for layout customization. Must be a stylex.create() value: not an inline style object like style={{}}.

主题 API

{
  "targets": [
    {
      "className": "astryx-table-header"
    }
  ]
}

Table Header Cell

<th> 包装器,读取 TableContext 以在 Table 内部使用时应用密度内边距、半粗字重、次要文本颜色和分隔线边框。
属性类型默认值说明
childrenReactNode表头单元格内容。

Table Row

<tr> 包装器,读取 TableContext 以在 Table 内部使用时应用条纹、悬停和分隔线样式。
属性类型默认值说明
children *ReactNode行单元格元素。

Table Selection Toolbar

Pass both outputs of useTableSelectionState to their matching consumers: selectionConfig to useTableSelection and selectionState to TableSelectionToolbar. Render product actions through startContent. Keep fixed or floating placement in the surrounding layout.

属性类型默认值说明
selection *TableSelectionStateShared selection state returned by useTableSelectionState: selectedKeys, selectedCount, hasSelection, and clearSelection.
startContentReactNodeProduct-owned action controls aligned to the logical start. Child controls inherit the toolbar size.
labelstringBulk actionsAccessible name for the action surface.
renderSelectionLabel(selectedCount: number) => ReactNodecount => ${count} selectedOverrides the visible count text for localisation or domain-specific copy.
clearLabelstringUnselect AllVisible label for the command that clears the complete controlled selection.
size'sm' | 'md' | 'lg'smToolbar size. Cascades to product action controls and the clear action.
variantSectionVariantmutedToolbar surface variant.
xstyleStyleXStylesCaller-owned layout styles, including sticky positioning for floating compositions.

使用建议

  • Pass selectionState directly from useTableSelectionState so the count and clear action stay synchronized with row selection.
  • Provide product actions as startContent instead of encoding them as configuration objects.
  • 避免:Inject the toolbar through the Table plugin or its horizontal scroll wrapper.

useTableColumnResize

返回 TablePlugin 的 Hook,为列表头边框添加可拖动的调整手柄。支持指针 + 键盘(WAI-ARIA 窗口分隔符)调整、RTL、每列最小/最大宽度,以及比例列保留(调整相邻列以保持表格全宽)。在释放时提交宽度。

属性类型默认值说明
columnWidthsRecord<string, number>受控的像素宽度覆盖,按列键索引。存在某键时会覆盖该列声明的宽度。
onColumnResizeEnd(updates: Record<string, number>) => void调整完成时(pointerup / Enter)调用。接收所有宽度发生变化的列键的映射——将其合并到你的 columnWidths 状态中。
minWidthnumber调整期间的全局最小列宽(像素)。设置后覆盖每列默认值。
maxWidthnumberInfinity调整期间的全局最大列宽(像素)。
columnsTableColumn<T>[]列定义——用于推导每列最小宽度,并区分比例列与像素列以确定相邻列/末列的调整行为。

useTableColumnSettings

无头列可见性和排序管理。提供筛选后的列、切换帮助方法和 MultiSelector 选项。

属性类型默认值说明
columns *ColumnSettingsOption[]所有可用列及其设置 UI 的元数据。每个条目包含 key、label、可选的 isAlwaysVisible 和 group。
activeColumnKeys *string[]当前活动的列键,按显示顺序排列。
onChangeActiveColumnKeys *(keys: string[]) => void活动列更改时调用。
defaultColumnKeysstring[]Default column set for "Reset to default". When omitted, reset shows all columns.

useTableFilterState

表格筛选的受管状态 hook。返回当前筛选映射和 onChange 处理程序。将结果直接传递给 useTableFiltering。

属性类型默认值说明
initialStateTableFilterState可选的初始筛选状态映射。

useTableFiltering

表格筛选插件,添加内联列过滤器(弹出式或内联控件)。与 useTableFilterState 配合使用管理状态。

属性类型默认值说明
filters *TableFilterState当前筛选状态——列键到筛选值的映射。
onFilterChange *(columnKey: string, value: TableFilterValue | null) => void用户更改筛选值时调用。null 清除筛选。
variant'popover' | 'inline' | 'inline-compact''popover'筛选控件的显示变体。

useTableGroupedRows

Hook that groups a flat data array into collapsible section rows. Each distinct groupBy value becomes a full-width section-header row with a chevron toggle, the group label, and a member count; collapsing hides that group's data rows while keeping the header visible. The disclosure state lives on the chevron button, not the native table row. Mirrors useTableTreeState: the consumer owns the collapsedGroups set and the hook returns {data, plugin, idKey}: pass them to Table as data, plugins, and idKey respectively. Grouping runs on the rows you hand it, so with pagination the order is filter, sort, slice, then group: sort by the group key first and the user's keys second, so a section's rows stay contiguous and each page appends to the bottom of the table instead of splicing rows in above the reader. A page that ends on a row count still cuts mid-section, and the heading then counts what has loaded rather than what exists: "6" quietly becoming "10". Where the full result set is in hand, carry the cut forward to the end of the section it lands in: every rendered section is then whole and its count is a total.

属性类型默认值说明
data *T[]The flat data to group.
groupBy *(item: T) => stringDerive the group key for a row. Rows with the same key share a section.
collapsedGroups *Set<string>Set of currently-collapsed group keys.
onToggleGroup *(groupKey: string) => voidCalled with a group key when its header is toggled.
renderGroupHeader(groupKey: string, count: number, collapsed: boolean) => ReactNodeCustom renderer for a group header's content (right of the chevron). Defaults to <groupKey> (<count>).
getRowKey(item: T) => stringStable key for a real row. Falls back to a positional key when omitted.
groupOrderstring[]Explicit group ordering; groups not listed keep first-seen order after these.

useTablePagination

Call useTablePagination with a config object containing page state and callback. Pass the returned plugin to Table via the plugins prop.

属性类型默认值说明
page *number当前页码(从 1 开始)。
onPageChange *(page: number) => void页面更改时调用。
totalItemsnumber所有页面的总项目数。用于计算总页数。
totalPagesnumberTotal number of pages. Use when you know the page count but not item count.
hasMorebooleanWhether more pages exist. Use for cursor-based pagination where the total is unknown.
pageSizenumber10每页项目数。
onPageSizeChange(pageSize: number) => voidCalled when the user changes the page size. Shows a page size dropdown when provided with pageSizeOptions.
pageSizeOptionsnumber[]Available page size options. Shows a page size selector when provided.
variant'pages' | 'count' | 'compact' | 'dots' | 'none''pages'分页控件的视觉变体。
position'below' | 'above' | 'both' | 'none''below'分页控件相对于表格的渲染位置。
align'start' | 'center' | 'end''center'Horizontal alignment of the pagination controls.
labelstring'Table pagination'Accessible name for the pagination nav landmark. With position='both' the two navs get distinct names (the label suffixed as '(top)' and '(bottom)') so same-type landmarks stay unique (axe landmark-unique).

useTableRowExpansion

Hook that returns a TablePlugin which expands a full-width detail panel below a row, rendered by the consumer via renderExpanded(item). Adds a leading chevron column and a right-click "Expand/Collapse row" action; the consumer owns the expandedKeys set. The panel spans the row but starts its content at the first column, past the chevron, so detail lines up with the labels above it; the inset follows the table density. Use it for master-detail rows (order details, forms, charts, nested tables). For hierarchical data where child rows reuse the parent columns, use useTableTreeData + useTableTreeState instead.

属性类型默认值说明
expandedKeys *Set<string>Set of currently-expanded row keys. Consumer-owned.
onToggle *(key: string) => voidCalled with a row key when its expansion is toggled (chevron click or context-menu action).
getRowKey *(item: T) => stringDerive a stable unique key from a row item.
renderExpanded *(item: T) => ReactNodeRender the detail content shown in a full-width panel below the row when it is expanded. Receives the row item.
getIsItemExpandable(item: T) => booleanControl which rows are expandable. Non-expandable rows show no chevron, no context-menu action, and never render a panel. Defaults to all rows expandable.
panelVariant'muted' | 'transparent''transparent'Background behind the detail panel. 'transparent' (default) paints nothing: the panel is the row's continuation and takes whatever surface the table sits on, so a Card shows through and a striped table's zebra reads on unchanged. 'muted' washes it, marking it as commentary rather than another row of data — reach for it in a bare table with no card, dividers or striping, where nothing else makes that distinction. The wash is a low-alpha near-black, so it is close to invisible over a dark card: 'muted' is largely a light-theme effect.
hasRowClickExpansionbooleanfalseToggle a row by clicking anywhere on it, not only on its chevron. A pointer-only convenience layered over the chevron, which stays the accessible control for keyboard and assistive tech. Clicks on interactive cell content (buttons, links, form controls) or on a text selection do not toggle, and non-expandable rows stay inert.

Master-detail order rows

const [expandedKeys, setExpandedKeys] = useState(new Set());
const expansion = useTableRowExpansion({
  expandedKeys,
  onToggle: key =>
    setExpandedKeys(prev => {
      const next = new Set(prev);
      next.has(key) ? next.delete(key) : next.add(key);
      return next;
    }),
  getRowKey: item => item.id,
  renderExpanded: item => <OrderDetails order={item} />,
});

<Table data={orders} columns={columns} idKey="id" plugins={{expansion}} />;

Migrating from tree rows to the tree plugin

// useTableRowExpansion is now for DETAIL PANELS, not tree rows. If you used
// it to render nested child rows that reuse the parent columns, move to
// useTableTreeData + useTableTreeState.

// BEFORE (tree rows via useTableRowExpansion + useTableRowExpansionState):
const {data, expansionConfig} = useTableRowExpansionState({
  baseData: tree,
  getChildren: item => item.children ?? [],
  getRowKey: item => item.id,
  expandedKeys,
  setExpandedKeys,
});
const expansion = useTableRowExpansion(expansionConfig);
<Table data={data} columns={columns} idKey="id" plugins={{expansion}} />;

// AFTER (tree rows via the tree plugin):
const {visibleData, treeConfig} = useTableTreeState({
  data: tree,                   // nested data
  idKey: 'id',                  // or a function: idKey={item => item.id}
  childrenKey: 'children',      // replaces getChildren
  defaultExpandedIds: ['root'], // or controlled: expandedIds + onExpandedIdsChange
});
const tree = useTableTreeData({
  ...treeConfig,
  hasExpandAllControl: true,    // was isAllExpanded + onToggleExpandAll
  hasRowClickExpansion: true,   // same prop name
});
<Table data={visibleData} columns={columns} idKey="id" plugins={{tree}} />;

useTableRowIndex

Hook that returns a TablePlugin which prepends a right-aligned, monospaced row-number column. Numbering follows the rendered data order (reflecting the current sort / filter / pagination view) and starts at 1 by default. Astryx renderCell receives only the row item, so the plugin takes the rendered data array to derive each ordinal.

属性类型默认值说明
data *T[]The data array currently rendered by the table (post sort/filter/page). Numbering follows this order.
getRowKey(item: T) => stringOptional key extractor returning a unique string per row. When provided, index lookup is keyed by the returned string; otherwise items are matched by reference identity. Memoize with useCallback for a stable plugin identity.
labelReactNode'#'Header label for the index column.
startFromnumber1First index value.

useTableRowStatus

Hook that returns a TablePlugin which prepends a narrow column signaling per-row status. Return {status, label} for a semantic success, warning, or error: Table resolves the matching glyph and tone through the active theme. Return {color, icon?, label} for a custom marker: every color is paint-only, an omitted icon renders the stable 8px dot, and an explicit icon renders that caller-selected glyph. Named custom icons keep their released Icon color mapping; raw CSS custom icons inherit the caller's exact paint instead of the previous primary fallback. Existing callers require no source migration. label is required and becomes the accessible image name plus supplemental hover tooltip; return null for no indicator. The column header is visually blank but carries a screen-reader-only localized name ("Row status", i18n key @astryx.table.rowStatus.columnHeader). Memoize getStatus with useCallback for a stable plugin identity.

属性类型默认值说明
getStatus *(item: T) => ({ status: 'success' | 'warning' | 'error'; color?: never; icon?: never; label: string } | { status?: never; color: 'accent' | 'success' | 'error' | 'warning' | 'red' | 'orange' | 'green' | 'yellow' | 'blue' | 'gray' | string; icon?: IconName; label: string }) | nullDerive either a semantic outcome or a custom marker. {status, label} accepts the closed success/error/warning vocabulary and resolves its glyph and tone through the active theme. {color, icon?, label} preserves the stable custom-marker path: color always selects paint, no icon renders an 8px dot, and icon renders the explicit caller glyph. Even color values named success/error/warning remain dots without icon. Valid icon names: close, chevronDown, chevronLeft, chevronRight, chevronsLeft, chevronsRight, check, success, error, warning, info, calendar, clock, externalLink, menu, moreHorizontal, search, upload, arrowUp, arrowDown, arrowsUpDown, funnel, eyeSlash, viewColumns, copy, checkDouble, wrench, stop, microphone. The branches are exclusive, label is required and announced via role="img", and null leaves the row status cell empty. Memoize with useCallback for a stable plugin identity.

useTableSelection

返回 TablePlugin 的 Hook,实现带复选框、全选和 aria-selected 的行选择功能。使用 React Context 实现独立的复选框重新渲染。

属性类型默认值说明
getIsItemSelected *(item: T) => boolean返回给定项是否当前被选中。
onSelectItem *(event: {item: T; isSelected: boolean}) => void当行复选框被切换时调用。isSelected 为新的期望状态。
onSelectAll *(event: {isAllSelected: boolean}) => void当全选表头复选框被切换时调用。
getIsAllSelected *() => boolean返回所有可选择项是否当前都被选中。
getIsIndeterminate() => boolean返回选择是否为部分选中(部分但非全部)。将全选复选框渲染为不确定状态。
getIsItemSelectable(item: T) => boolean() => true返回行是否应显示复选框。不可选择的行在选择单元格中不渲染任何内容。
getIsItemEnabled(item: T) => boolean() => true返回行复选框是否可交互。禁用的行显示禁用状态的复选框。
getRowLabel(item: T) => string为行派生人类可读的标识;行复选框的隐藏标签变为 Select ${getRowLabel(item)},让屏幕阅读器播报每个复选框选择的是哪一行。省略时回退为 "Select row"。
hasRowHighlightbooleantrue为选中的行绘制强调色背景。当周围的界面已用行背景表达其他含义(例如该行已在详情面板中打开)时设为 false —— 该背景是内联样式,无法从业务代码覆盖。仅去掉背景:无论如何选中的行仍会设置 aria-selected。

Accessible per-row checkbox names

const selectionPlugin = useTableSelection({
  getIsItemSelected: item => selectedIds.has(item.id),
  onSelectItem: ({item, isSelected}) => toggle(item.id, isSelected),
  onSelectAll: ({isAllSelected}) => selectAll(isAllSelected),
  getIsAllSelected: () => selectedIds.size === users.length,
  // Screen readers announce "Select Alice", "Select Bob", ...
  // instead of an undifferentiated "Select row" for every checkbox.
  getRowLabel: item => item.name,
});

<Table
  data={users}
  columns={columns}
  idKey="id"
  plugins={{selection: selectionPlugin}}
/>;

Opt out of the checked-row wash

// This table already uses row background to mean "open in the detail
// panel". Turning the selection wash off keeps that meaning unambiguous;
// the checkbox and aria-selected still carry the selection.
const selectionPlugin = useTableSelection({
  getIsItemSelected: item => selectedIds.has(item.id),
  onSelectItem: ({item, isSelected}) => toggle(item.id, isSelected),
  onSelectAll: ({isAllSelected}) => selectAll(isAllSelected),
  getIsAllSelected: () => selectedIds.size === users.length,
  hasRowHighlight: false,
});

useTableSelectionState

State management companion for useTableSelection. Returns selectionConfig for the behaviour plugin plus selectionState for selection-aware UI such as TableSelectionToolbar. Handles disabled/selectable row filtering for select-all automatically: disabled rows are frozen (preserved across select-all/deselect-all), non-selectable rows are excluded.

属性类型默认值说明
data *T[]The full data array rendered in the table.
idKey *(keyof T & string) | ((item: T) => string)Key extractor: property name or function returning a unique string ID.
selectedKeys *Set<string>Controlled set of selected item IDs.
setSelectedKeys *Dispatch<SetStateAction<Set<string>>>Setter for the controlled selected keys.
getIsItemSelectable(item: T) => boolean() => trueShould this row show a checkbox? Non-selectable rows are excluded from select-all.
getIsItemEnabled(item: T) => boolean() => trueIs this row checkbox interactive? Disabled rows are frozen: select-all preserves their state.

useTableSortable

Call useTableSortable with a config object containing sort state and callback. Pass the returned plugin to Table via the plugins prop.

属性类型默认值说明
sort *Array<{sortKey: TSortKey, direction: 'ascending' | 'descending'}>当前排序状态。{sortKey, direction} 条目的有序数组。第一个条目是主排序。方向值为完整单词 'ascending' / 'descending',不是 'asc' / 'desc'。
onSortChange *(sort: Array<{sortKey: TSortKey, direction: 'ascending' | 'descending'}>) => void用户点击表头单元格更改排序时调用。
allowUnsortedStatebooleanfalse允许循环回到未排序状态。为 true 时:'ascending'、'descending'、未排序。为 false 时:'ascending'、'descending'、'ascending'。
isMultiSortEnabledbooleanfalse通过 Shift+点击启用多列排序。普通点击仍替换整个排序状态。

useTableStickyColumns

返回 TablePlugin 的 Hook,将一组连续的列固定到表格的起始和/或结束边缘。固定的列具有累积偏移量,并在滚动内容上方显示柔和的、随滚动感知的阴影。空配置 ({}) 是有效的空操作,不固定任何列。

属性类型默认值说明
startKeysstring[]固定到起始(inline-start / LTR 中为左侧)边缘的列键——从第一列到最后列出的键的连续范围。
endKeysstring[]固定到结束(inline-end / LTR 中为右侧)边缘的列键——从第一个列出的键到最后一列的连续范围。

useTableTreeData

Headless tree plugin for Table: renders nested rows with per-level indentation and expand/collapse chevrons in the tree column (the first column by default), and reflects hierarchy on body rows via aria-level and aria-expanded. Composable with the other Table plugins: the canonical plugin order places tree before selection, so the checkbox column lands left of the indented tree column. Feed it the treeConfig from useTableTreeState, or construct the config directly for server-driven or pre-flattened trees. When no row is expandable (flat data), every transform is a pass-through and the table renders identically to one without the plugin. Known limitation: the tree column wraps its cell content, so textOverflow="truncate" tooltips do not apply within the tree column.

属性类型默认值说明
getRowMeta *(item: T) => TableTreeRowMeta | undefinedStructural meta for a visible row: {id, level (0-based), hasChildren, isExpanded}.
onToggleItem *(item: T) => voidToggle a row's expansion.
hasExpandableRows *booleanWhether any row in the dataset is expandable. When false the plugin is a no-op: no expanders, no indent, no tree ARIA.
hasExpandAllControlbooleanfalseShow an expand-all/collapse-all toggle in the tree column header. Requires isAllExpanded plus onExpandAll/onCollapseAll (all supplied by useTableTreeState).
isAllExpandedboolean | 'indeterminate'Aggregate expansion state across every expandable row, driving the header expand-all toggle. true when all are expanded, false when none are, indeterminate when some are.
onExpandAll() => voidExpand every expandable row. Wired to the header control.
onCollapseAll() => voidCollapse every row. Wired to the header control.
indent'sm' | 'md' | 'lg''md'Indent step per level, mapped to the spacing-3 / spacing-4 / spacing-6 tokens.
treeColumnKeystringColumn that carries the indent + expander. Defaults to the first column.
hasRowClickExpansionbooleanfalseWhen true, clicking anywhere on an expandable row toggles its expansion, in addition to the chevron. A pointer-only convenience: keyboard and assistive-tech users toggle via the chevron button. Clicks on interactive cell content (buttons, links, form controls) or a text selection do not toggle. Leaf rows stay inert, and it is a no-op on flat data.

File tree with expandable folders

const {visibleData, treeConfig} = useTableTreeState({
  data: files,          // rows may nest under 'children'
  idKey: 'id',
  defaultExpandedIds: ['src'],
});
const tree = useTableTreeData(treeConfig);

<Table data={visibleData} columns={columns} idKey="id" plugins={{tree}} />;

useTableTreeState

State management companion for useTableTreeData. Owns the expanded set (controlled or uncontrolled) and flattens nested data into the visible row array; collapsed subtrees are unmounted, not hidden, so the table body contains exactly the visible rows. Returns expandAll/collapseAll helpers, the aggregate isAllExpanded state (true/false/indeterminate) for a header expand-all control, and a ready-to-use config for the tree plugin. Note: because collapsed rows unmount, cell-local React state inside collapsed subtrees is lost on collapse; lift state that must survive.

属性类型默认值说明
data *T[]Nested data: rows may carry child rows under childrenKey. Flat data (no children anywhere) makes the plugin a no-op, so it can be adopted before the data becomes hierarchical.
idKey *(keyof T & string) | ((item: T) => string | number)Row ID accessor: property name or function returning a unique id.
childrenKeystring'children'Property holding each row's children array.
defaultExpandedIdsIterable<string>Initial expanded row ids for uncontrolled mode. Ignored when expandedIds is provided.
expandedIdsReadonlySet<string>Controlled set of expanded row ids. Pair with onExpandedIdsChange.
onExpandedIdsChange(ids: ReadonlySet<string>) => voidCalled with the next expanded set whenever expansion changes (both modes). In lazy-loading setups, trigger the children fetch here.
isItemExpandable(item: T) => booleanShould this row show an expander? Overrides the default non-empty-children check; use for lazy loading, where a row is expandable before its children have been fetched.
sortSiblings(siblings: T[]) => T[]Sort each sibling group independently during flattening; children always stay directly under their parent. Pass applySort from useTableSortableState to compose with column sorting.
indent'sm' | 'md' | 'lg''md'Indent step per level (spacing-3 / spacing-4 / spacing-6), forwarded to useTableTreeData.
treeColumnKeystringColumn that carries the indent + expander, forwarded to useTableTreeData. Defaults to the first column.

示例的 StyleX 编译要求

TableFloatingBulkActionsTable 包含 stylex.create,需要配置 StyleX 源码编译流程。仅引入 Core 预编译 CSS 不足以运行这些示例样式,复制前请阅读样式与 Tailwind。