Skip to content

Table

Data table with sorting, filtering, and column resize plugins.

TaskOwnerStatusBudgetDue
Design reviewAda LovelaceIn progress18200Jun 12
Billing migrationGrace HopperReady9600Jun 17
QA passKatherine JohnsonBlocked13400Jun 21
Docs refreshHedy LamarrDone7200Jun 24
Alert tuningAlan TuringIn progress15100Jun 27
Research synthesisMary JacksonReady5400Jul 2
<Table columns={columns} data={data} idKey="id" />
TaskOwnerStatusBudgetDue

No tasks found

Try adjusting filters or creating a new task.
<Table
columns={columns}
data={[]}
emptyState={
<EmptyState
description="Try adjusting filters or creating a new task."
isCompact
title="No tasks found"
/>
}
/>
TaskOwnerStatusBudgetDue

No data

<Table columns={columns} data={[]} />
TaskOwnerStatusBudgetDue
Design reviewAda LovelaceIn progress18200Jun 12
Billing migrationGrace HopperReady9600Jun 17
TaskOwnerStatusBudgetDue
Design reviewAda LovelaceIn progress18200Jun 12
Billing migrationGrace HopperReady9600Jun 17
TaskOwnerStatusBudgetDue
Design reviewAda LovelaceIn progress18200Jun 12
Billing migrationGrace HopperReady9600Jun 17
function DensityStory() {
return (
<div style={{display: 'grid', gap: 24}}>
<Table columns={columns} data={data.slice(0, 2)} density="compact" />
<Table columns={columns} data={data.slice(0, 2)} density="balanced" />
<Table columns={columns} data={data.slice(0, 2)} density="spacious" />
</div>
);
}
<DensityStory />
SourceCost
Compute$18.00
Storage$6.00
Total$24.00
SourceCost
Compute$18.00
Storage$6.00
Total$24.00
SourceCost
Compute$18.00
Storage$6.00
Total$24.00
SourceCost
Compute$18.00
Storage$6.00
Total$24.00
function DividersStory() {
const variants: TableDividers[] = ['rows', 'columns', 'grid', 'none'];
return (
<div style={{display: 'grid', gap: 24}}>
{variants.map(variant => (
<Table dividers={variant} key={variant} label={`${variant} dividers`}>
<TableHeader>
<TableRow>
<TableHeaderCell>Source</TableHeaderCell>
<TableHeaderCell>Cost</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Compute</TableCell>
<TableCell>$18.00</TableCell>
</TableRow>
<TableRow>
<TableCell>Storage</TableCell>
<TableCell>$6.00</TableCell>
</TableRow>
</TableBody>
<TableFooter>
<TableRow>
<TableCell>Total</TableCell>
<TableCell>$24.00</TableCell>
</TableRow>
</TableFooter>
</Table>
))}
</div>
);
}
<DividersStory />
TaskOwnerStatusBudgetDue
Design reviewAda LovelaceIn progress18200Jun 12
Billing migrationGrace HopperReady9600Jun 17
QA passKatherine JohnsonBlocked13400Jun 21
Docs refreshHedy LamarrDone7200Jun 24
Alert tuningAlan TuringIn progress15100Jun 27
Research synthesisMary JacksonReady5400Jul 2
<Table columns={columns} data={data} idKey="id" isStriped />
TaskOwnerStatusBudgetDue
Design reviewAda LovelaceIn progress18200Jun 12
Billing migrationGrace HopperReady9600Jun 17
QA passKatherine JohnsonBlocked13400Jun 21
Docs refreshHedy LamarrDone7200Jun 24
Alert tuningAlan TuringIn progress15100Jun 27
Research synthesisMary JacksonReady5400Jul 2
<Table columns={columns} data={data} hasHover idKey="id" />
PlanStatus
StarterReady
GrowthReady
EnterpriseDraft
3 plans
<Table hasHover isStriped>
<TableHeader>
<TableRow>
<TableHeaderCell>Plan</TableHeaderCell>
<TableHeaderCell>Status</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Starter</TableCell>
<TableCell>Ready</TableCell>
</TableRow>
<TableRow>
<TableCell>Growth</TableCell>
<TableCell>Ready</TableCell>
</TableRow>
<TableRow>
<TableCell>Enterprise</TableCell>
<TableCell>Draft</TableCell>
</TableRow>
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={2}>3 plans</TableCell>
</TableRow>
</TableFooter>
</Table>
TaskNotesOwner
Design reviewUnbrokenIdentifier_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ with surrounding copy to compare truncation and wrapping.Ada Lovelace
Billing migrationMove remaining accounts to the new billing pipeline after validating invoice previews with support.Grace Hopper
QA passRun the cross-browser verification pass and log issues against the release checklist.Katherine Johnson
TaskNotesOwner
Design reviewUnbrokenIdentifier_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ with surrounding copy to compare truncation and wrapping.Ada Lovelace
Billing migrationMove remaining accounts to the new billing pipeline after validating invoice previews with support.Grace Hopper
QA passRun the cross-browser verification pass and log issues against the release checklist.Katherine Johnson
function TextOverflowStory() {
const overflowData = data.slice(0, 3).map((item, index) =>
index === 0
? {
...item,
notes:
'UnbrokenIdentifier_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ with surrounding copy to compare truncation and wrapping.',
}
: item,
);
return (
<div style={{display: 'grid', gap: 24}}>
<Table
columns={longTextColumns}
data={overflowData}
textOverflow="truncate"
/>
<Table
columns={longTextColumns}
data={overflowData}
textOverflow="wrap"
/>
</div>
);
}
<TextOverflowStory />
TaskNotesOwner
Design reviewPrepare the final handoff package with annotated screenshots, links to key decisions, and open follow-up items.Ada Lovelace
Billing migrationMove remaining accounts to the new billing pipeline after validating invoice previews with support.Grace Hopper
TaskNotesOwner
Design reviewPrepare the final handoff package with annotated screenshots, links to key decisions, and open follow-up items.Ada Lovelace
Billing migrationMove remaining accounts to the new billing pipeline after validating invoice previews with support.Grace Hopper
TaskNotesOwner
Design reviewPrepare the final handoff package with annotated screenshots, links to key decisions, and open follow-up items.Ada Lovelace
Billing migrationMove remaining accounts to the new billing pipeline after validating invoice previews with support.Grace Hopper
function VerticalAlignStory() {
return (
<div style={{display: 'grid', gap: 24}}>
<Table
columns={longTextColumns}
data={data.slice(0, 2)}
verticalAlign="top"
/>
<Table
columns={longTextColumns}
data={data.slice(0, 2)}
verticalAlign="middle"
/>
<Table
columns={longTextColumns}
data={data.slice(0, 2)}
verticalAlign="bottom"
/>
</div>
);
}
<VerticalAlignStory />
Fixed 160px2fr min 180px1fr min 120pxFixed 96px
Design reviewPrepare the final handoff package with annotated screenshots, links to key decisions, and open follow-up items.Ada LovelaceJun 12
Billing migrationMove remaining accounts to the new billing pipeline after validating invoice previews with support.Grace HopperJun 17
QA passRun the cross-browser verification pass and log issues against the release checklist.Katherine JohnsonJun 21
Docs refreshRefresh the administrator guide and add examples for importing historical project data.Hedy LamarrJun 24
Alert tuningTune noisy alert thresholds and group notifications by service owner for faster triage.Alan TuringJun 27
Research synthesisInterview workflow owners and summarize the strongest opportunities for dashboard consolidation.Mary JacksonJul 2
<Table columns={widthColumns} data={data} idKey="id" />
TaskOwnerPriorityStatus
Design reviewAda LovelaceHighIn progress
Billing migrationGrace HopperMediumReady
QA passKatherine JohnsonHighBlocked
Docs refreshHedy LamarrLowDone
Alert tuningAlan TuringMediumIn progress
Research synthesisMary JacksonLowReady
<Table
columns={[
columns[0],
columns[1],
{
header: 'Priority',
key: 'priority',
renderCell: item => (
<Badge
color={
item.priority === 'High'
? 'error'
: item.priority === 'Medium'
? 'warning'
: 'neutral'
}
label={item.priority}
size="lg"
/>
),
},
columns[2],
]}
data={data}
idKey="id"
/>
BudgetDueIdNotesOwnerPriorityProgressStatusTask
18200Jun 12designPrepare the final handoff package with annotated screenshots, links to key decisions, and open follow-up items.Ada LovelaceHigh70%In progressDesign review
9600Jun 17migrationMove remaining accounts to the new billing pipeline after validating invoice previews with support.Grace HopperMedium45%ReadyBilling migration
13400Jun 21qaRun the cross-browser verification pass and log issues against the release checklist.Katherine JohnsonHigh20%BlockedQA pass
7200Jun 24docsRefresh the administrator guide and add examples for importing historical project data.Hedy LamarrLow100%DoneDocs refresh
<Table data={data.slice(0, 4)} idKey="id" />
PlanStatusOwner
Manual rowsReadyAda Lovelace
Rendered with table primitives.
<Table>
<TableHeader>
<TableRow>
<TableHeaderCell>Plan</TableHeaderCell>
<TableHeaderCell>Status</TableHeaderCell>
<TableHeaderCell>Owner</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Manual rows</TableCell>
<TableCell>Ready</TableCell>
<TableCell>Ada Lovelace</TableCell>
</TableRow>
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>Rendered with table primitives.</TableCell>
</TableRow>
</TableFooter>
</Table>
WorkstreamOwnerStatusNext step
DocumentationAlex MorganReady for reviewPublish the migration guide and confirm that every example uses the stable API.
AccessibilitySam RiveraFollow-up neededRecheck keyboard navigation at compact viewport sizes before the release candidate is tagged.
<div style={{maxWidth: '100%', width: '560px'}}>
<Table
label="Release readiness"
style={{minWidth: '760px'}}
verticalAlign="top">
<TableHeader>
<TableRow>
<TableHeaderCell style={{width: '150px'}}>
Workstream
</TableHeaderCell>
<TableHeaderCell style={{width: '130px'}}>Owner</TableHeaderCell>
<TableHeaderCell style={{width: '170px'}}>Status</TableHeaderCell>
<TableHeaderCell style={{width: '310px'}}>
Next step
</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Documentation</TableCell>
<TableCell>Alex Morgan</TableCell>
<TableCell>
<Badge color="success" label="Ready for review" size="lg" />
</TableCell>
<TableCell>
Publish the migration guide and confirm that every example uses
the stable API.
</TableCell>
</TableRow>
<TableRow>
<TableCell>Accessibility</TableCell>
<TableCell>Sam Rivera</TableCell>
<TableCell>
<Badge color="warning" label="Follow-up needed" size="lg" />
</TableCell>
<TableCell>
Recheck keyboard navigation at compact viewport sizes before the
release candidate is tagged.
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
Design reviewAda LovelaceIn progress18200Jun 12
Billing migrationGrace HopperReady9600Jun 17
QA passKatherine JohnsonBlocked13400Jun 21
Docs refreshHedy LamarrDone7200Jun 24
Alert tuningAlan TuringIn progress15100Jun 27
Research synthesisMary JacksonReady5400Jul 2
function SortableStory() {
const sortable = useTableSortableState<TaskRow, string>({
data,
isMultiSortEnabled: true,
});
const sortPlugin = useTableSortable<TaskRow>(sortable.sortConfig);
return (
<Table
columns={columns}
data={sortable.sortedData}
idKey="id"
plugins={{sortPlugin}}
/>
);
}
<SortableStory />
Design review70%18200
Alert tuning55%15100
QA pass20%13400
Billing migration45%9600
Docs refresh100%7200
Research synthesis10%5400
function SortableAlignmentStory({
direction,
}: {
direction: 'ltr' | 'rtl';
}): React.JSX.Element {
const sortable = useTableSortableState<TaskRow, string>({
data,
defaultSort: [{direction: 'descending', sortKey: 'budget'}],
isMultiSortEnabled: true,
});
const sortPlugin = useTableSortable<TaskRow>(sortable.sortConfig);
return (
<div dir={direction}>
<Table
columns={alignedSortableColumns}
data={sortable.sortedData}
idKey="id"
plugins={{sortPlugin}}
/>
</div>
);
}
<SortableAlignmentStory direction="ltr" />
Design review70%18200
Alert tuning55%15100
QA pass20%13400
Billing migration45%9600
Docs refresh100%7200
Research synthesis10%5400
function SortableAlignmentStory({
direction,
}: {
direction: 'ltr' | 'rtl';
}): React.JSX.Element {
const sortable = useTableSortableState<TaskRow, string>({
data,
defaultSort: [{direction: 'descending', sortKey: 'budget'}],
isMultiSortEnabled: true,
});
const sortPlugin = useTableSortable<TaskRow>(sortable.sortConfig);
return (
<div dir={direction}>
<Table
columns={alignedSortableColumns}
data={sortable.sortedData}
idKey="id"
plugins={{sortPlugin}}
/>
</div>
);
}
<SortableAlignmentStory direction="rtl" />
TaskOwnerStatusBudgetDue
Design reviewAda LovelaceIn progress18200Jun 12
Billing migrationGrace HopperReady9600Jun 17
QA passKatherine JohnsonBlocked13400Jun 21
Docs refreshHedy LamarrDone7200Jun 24
Alert tuningAlan TuringIn progress15100Jun 27
Research synthesisMary JacksonReady5400Jul 2
function SelectionStory() {
const [selectedKeys, setSelectedKeys] = useState(
() => new Set<string>(['design']),
);
const selection = useTableSelectionState({
data,
getIsItemEnabled: item => item.status !== 'Blocked',
getIsItemSelectable: item => item.status !== 'Done',
idKey: 'id',
selectedKeys,
setSelectedKeys,
});
const selectionPlugin = useTableSelection(selection.selectionConfig);
return (
<Table
columns={columns}
data={data}
idKey="id"
plugins={{selectionPlugin}}
/>
);
}
<SelectionStory />
NameDOB
Alex Morganalex.morgan@example.com
1980-01-19
Sam Riverasam.rivera@example.com
1983-09-27
Jordan Leejordan.lee@example.com
—
Taylor Kimtaylor.kim@example.com
1994-08-22
function MultilineSelectionStory() {
const [selectedKeys, setSelectedKeys] = useState(
() => new Set(contactData.map(item => item.id)),
);
const selection = useTableSelectionState({
data: contactData,
idKey: 'id',
selectedKeys,
setSelectedKeys,
});
const selectionPlugin = useTableSelection(selection.selectionConfig);
return (
<Table
columns={contactColumns}
data={contactData}
idKey="id"
plugins={{selectionPlugin}}
verticalAlign="top"
/>
);
}
<MultilineSelectionStory />
NameDOB
Alex Morganalex.morgan@example.com
1980-01-19
Sam Riverasam.rivera@example.com
1983-09-27
Jordan Leejordan.lee@example.com
—
Taylor Kimtaylor.kim@example.com
1994-08-22
function StripedSelectionStory() {
const [selectedKeys, setSelectedKeys] = useState(
() => new Set([contactData[0].id, contactData[2].id]),
);
const selection = useTableSelectionState({
data: contactData,
idKey: 'id',
selectedKeys,
setSelectedKeys,
});
const selectionPlugin = useTableSelection(selection.selectionConfig);
return (
<Table
columns={contactColumns}
data={contactData}
idKey="id"
isStriped
plugins={{selectionPlugin}}
verticalAlign="top"
/>
);
}
<StripedSelectionStory />
TaskOwnerStatusBudgetDue
Design reviewAda LovelaceIn progress18200Jun 12
Billing migrationGrace HopperReady9600Jun 17
QA passKatherine JohnsonBlocked13400Jun 21
Docs refreshHedy LamarrDone7200Jun 24
Alert tuningAlan TuringIn progress15100Jun 27
Research synthesisMary JacksonReady5400Jul 2
function SelectionMinimumWidthStory() {
const [selectedKeys, setSelectedKeys] = useState(() => new Set<string>());
const selection = useTableSelectionState({
data,
idKey: 'id',
selectedKeys,
setSelectedKeys,
});
const selectionPlugin = useTableSelection(selection.selectionConfig);
return (
<div style={{maxWidth: '100%', width: '560px'}}>
<Table
columns={[
{header: 'Task', key: 'task'},
{header: 'Owner', key: 'owner'},
{header: 'Status', key: 'status'},
{align: 'end', header: 'Budget', key: 'budget'},
{header: 'Due', key: 'due'},
]}
data={data}
idKey="id"
plugins={{selectionPlugin}}
style={{minWidth: '760px'}}
/>
</div>
);
}
<SelectionMinimumWidthStory />
NameKindSizeModified
srcfolder—Jun 20
components
folder—Jun 20
utils
folder—Jun 17
index.ts
file0.4 KBJun 20
publicfolder—Jun 1
package.jsonfile1.8 KBJun 22
README.mdfile0.6 KBJun 1
function RowExpansionStory() {
const expansion = useTableRowExpansionState<FileRow>({
data: fileTree,
defaultExpandedKeys: ['src'],
getChildren: item => item.children,
getRowKey: item => item.id,
});
const expansionPlugin = useTableRowExpansion<FileRow>({
...expansion.expansionConfig,
hasExpandAllToggle: true,
});
return (
<Table
columns={fileColumns}
data={expansion.data}
hasHover
idKey="id"
plugins={{expansion: expansionPlugin}}
/>
);
}
<RowExpansionStory />
NameKindSizeModified
srcfolder—Jun 20
publicfolder—Jun 1
package.jsonfile1.8 KBJun 22
README.mdfile0.6 KBJun 1
function RowExpansionRowClickStory() {
const expansion = useTableRowExpansionState<FileRow>({
data: fileTree,
getChildren: item => item.children,
getRowKey: item => item.id,
});
const expansionPlugin = useTableRowExpansion<FileRow>({
...expansion.expansionConfig,
getExpanderLabel: (item, isExpanded) =>
isExpanded ? `Collapse ${item.name}` : `Expand ${item.name}`,
hasRowClickExpansion: true,
});
return (
<Table
columns={fileColumns}
data={expansion.data}
hasHover
idKey="id"
plugins={{expansion: expansionPlugin}}
/>
);
}
<RowExpansionRowClickStory />
TaskOwnerStatusBudgetDue
Design reviewAda LovelaceIn progress18200Jun 12
Billing migrationGrace HopperReady9600Jun 17
function PaginationStory() {
const [page, setPage] = useState(1);
const pageSize = 2;
const paginationPlugin = useTablePagination<TaskRow>({
onPageChange: setPage,
page,
pageSize,
position: 'both',
totalItems: data.length,
variant: 'pages',
});
return (
<Table
columns={columns}
data={paginateData(data, {page, pageSize})}
idKey="id"
plugins={{paginationPlugin}}
/>
);
}
<PaginationStory />
TaskOwnerStatusDue
Design reviewAda LovelaceIn progressJun 12
Billing migrationGrace HopperReadyJun 17
QA passKatherine JohnsonBlockedJun 21
Docs refreshHedy LamarrDoneJun 24
Alert tuningAlan TuringIn progressJun 27
Research synthesisMary JacksonReadyJul 2
function ColumnSettingsStory() {
const defaultKeys = ['task', 'owner', 'status', 'due'];
const [activeColumnKeys, setActiveColumnKeys] = useState(defaultKeys);
const settingsPlugin = useTableColumnSettings<TaskRow>({
activeColumnKeys,
columns: [
{isAlwaysVisible: true, key: 'task', label: 'Task'},
{key: 'owner', label: 'Owner'},
{key: 'status', label: 'Status'},
{key: 'budget', label: 'Budget'},
{key: 'due', label: 'Due'},
],
defaultColumnKeys: defaultKeys,
onChangeActiveColumnKeys: keys => {
setActiveColumnKeys([...keys]);
},
});
return (
<div style={{display: 'grid', gap: 12}}>
<div style={{display: 'flex', gap: 8}}>
<Button
icon={Settings2}
label="Show budget"
onClick={() => {
setActiveColumnKeys(['task', 'owner', 'status', 'budget', 'due']);
}}
size="sm"
variant="secondary"
/>
<Button
icon={Settings2}
label="Reorder"
onClick={() => {
setActiveColumnKeys(['status', 'task', 'owner', 'due']);
}}
size="sm"
variant="secondary"
/>
<Button
icon={RotateCcw}
label="Reset"
onClick={() => {
setActiveColumnKeys(defaultKeys);
}}
size="sm"
variant="ghost"
/>
</div>
<Table
columns={columns}
data={data}
idKey="id"
plugins={{settingsPlugin}}
/>
</div>
);
}
<ColumnSettingsStory />
TaskOwnerStatusBudgetDue
Design reviewAda LovelaceIn progress18200Jun 12
Billing migrationGrace HopperReady9600Jun 17
QA passKatherine JohnsonBlocked13400Jun 21
Docs refreshHedy LamarrDone7200Jun 24
Alert tuningAlan TuringIn progress15100Jun 27
Research synthesisMary JacksonReady5400Jul 2
function ColumnResizeStory() {
const [columnWidths, setColumnWidths] = useState<Record<string, number>>({
due: 128,
task: 220,
owner: 180,
});
const resizePlugin = useTableColumnResize<TaskRow>({
columnWidths,
columns: columns as TableColumn<Record<string, unknown>>[],
minWidth: 96,
onColumnResizeEnd: updates => {
setColumnWidths(previous => ({...previous, ...updates}));
},
});
return (
<Table columns={columns} data={data} idKey="id" plugins={{resizePlugin}} />
);
}
<ColumnResizeStory />
Task
Owner
Status
BudgetDue
Design reviewAda LovelaceIn progress18200Jun 12
Billing migrationGrace HopperReady9600Jun 17
QA passKatherine JohnsonBlocked13400Jun 21
Docs refreshHedy LamarrDone7200Jun 24
Alert tuningAlan TuringIn progress15100Jun 27
Research synthesisMary JacksonReady5400Jul 2
function FilteringStory() {
return <FilteredTable variant="popover" />;
}
function FilteredTable({variant}: {variant: TableFilterVariant}) {
const {filters, onFilterChange} = useTableFilterState();
const filteredRows = useMemo(() => filterData(data, filters), [filters]);
const filteringPlugin = useTableFiltering<TaskRow>({
filters,
onFilterChange,
searchConfig: filterSearchConfig,
variant,
});
return (
<Table
columns={filterColumns}
data={filteredRows}
emptyState={<EmptyState isCompact title="No matching tasks" />}
idKey="id"
plugins={{filteringPlugin}}
/>
);
}
<FilteringStory />
Task
Owner
Status
BudgetDue
Design reviewAda LovelaceIn progress18200Jun 12
Billing migrationGrace HopperReady9600Jun 17
QA passKatherine JohnsonBlocked13400Jun 21
Docs refreshHedy LamarrDone7200Jun 24
Alert tuningAlan TuringIn progress15100Jun 27
Research synthesisMary JacksonReady5400Jul 2
function InlineFilteringStory() {
return <FilteredTable variant="inline" />;
}
function FilteredTable({variant}: {variant: TableFilterVariant}) {
const {filters, onFilterChange} = useTableFilterState();
const filteredRows = useMemo(() => filterData(data, filters), [filters]);
const filteringPlugin = useTableFiltering<TaskRow>({
filters,
onFilterChange,
searchConfig: filterSearchConfig,
variant,
});
return (
<Table
columns={filterColumns}
data={filteredRows}
emptyState={<EmptyState isCompact title="No matching tasks" />}
idKey="id"
plugins={{filteringPlugin}}
/>
);
}
<InlineFilteringStory />
Research synthesisMary JacksonReady5400Jul 2
Design reviewAda LovelaceIn progress18200Jun 12
Annotate screens
Ada LovelaceIn progress4200Jun 10
function CombinedPluginsStory() {
const [page, setPage] = useState(1);
const [selectedKeys, setSelectedKeys] = useState(() => new Set<string>());
const {filters, onFilterChange} = useTableFilterState();
// Composition order: filter and sort operate on the root rows of the tree,
// expansion flattens the visible rows, and pagination slices the flattened
// output last (paginating before flattening would orphan child rows).
const filteredRows = useMemo(
() => filterData(combinedTaskTree, filters),
[filters],
);
const sortable = useTableSortableState<TaskRow, string>({
data: filteredRows,
defaultSort: [{direction: 'ascending', sortKey: 'due'}],
});
const expansion = useTableRowExpansionState<TaskRow>({
data: sortable.sortedData,
defaultExpandedKeys: ['design'],
getChildren: item => item.subtasks,
getRowKey: item => item.id,
});
const selection = useTableSelectionState({
data: expansion.data,
idKey: 'id',
selectedKeys,
setSelectedKeys,
});
const pageSize = 3;
const sortPlugin = useTableSortable<TaskRow>(sortable.sortConfig);
const expansionPlugin = useTableRowExpansion(expansion.expansionConfig);
const selectionPlugin = useTableSelection(selection.selectionConfig);
const filteringPlugin = useTableFiltering<TaskRow>({
filters,
onFilterChange,
searchConfig: filterSearchConfig,
variant: 'popover',
});
const paginationPlugin = useTablePagination<TaskRow>({
onPageChange: setPage,
page,
pageSize,
totalItems: expansion.data.length,
variant: 'compact',
});
return (
<Table
columns={filterColumns}
data={paginateData(expansion.data, {page, pageSize})}
idKey="id"
plugins={{
expansionPlugin,
selectionPlugin,
sortPlugin,
filteringPlugin,
paginationPlugin,
}}
/>
);
}
<CombinedPluginsStory />
PropTypeDefaultDescription
childrenReactNode—Table primitives rendered instead of data-driven content. When provided, columns/data are ignored and the children are rendered as-is.
classNamestring—Additional CSS class names applied to the table element.
columnsTableColumn<T>[]—Column definitions. When omitted, columns are inferred from data.
dataT[]—Row data rendered into the table body.
data-testidstring—Test ID applied to the table element.
density"compact" | "balanced" | "spacious"'balanced'Spacing density for rows and cells.
dividers"none" | "grid" | "columns" | "rows"'rows'Which divider lines to show between cells.
emptyStateReactNode—Content shown when data is an empty array. Omit for the default empty state; pass null to render nothing.
hasHoverbooleanfalseWhether rows highlight on hover.
idKey(keyof T & string) | ((item: T) => number | string)—Stable row identity, used as the React key for each body row.
isStripedbooleanfalseWhether alternating rows have a subtle background.
labelstring'Table'Name of the table, used to label its horizontal scroll region.
pluginsRecord<string, TablePlugin<T>> | TablePlugin<T>[]—Plugin map or array that extends table behavior.
refRef<HTMLTableElement>—Ref forwarded to the table element.
styleCSSProperties—Inline styles applied to the table element.
tablePropsHTMLAttributes<HTMLTableElement>—Extra HTML attributes spread onto the table element.
textOverflow"wrap" | "truncate"'wrap'How overflowing cell text is handled.
verticalAlign"bottom" | "top" | "middle"'middle'Vertical alignment of cell content.

Wraps table body rows in a `<tbody>` element.

PropTypeDefaultDescription
children*ReactNode—Table row elements rendered inside the body.
classNamestring—Additional CSS class names applied to the tbody.
data-testidstring—Test ID applied to the tbody.
refRef<HTMLTableSectionElement>—Ref forwarded to the tbody element.
styleCSSProperties—Inline styles applied to the tbody.

Table data cell with density, alignment, and divider styling from context.

PropTypeDefaultDescription
childrenReactNode—Cell content.
classNamestring—Additional CSS class names applied to the td.
colSpannumber—Number of columns the cell spans.
data-testidstring—Test ID applied to the td.
headersstring—Space-separated list of header cell IDs this cell relates to.
refRef<HTMLTableCellElement>—Ref forwarded to the td element.
rowSpannumber—Number of rows the cell spans.
styleCSSProperties—Inline styles applied to the td.

Wraps table footer rows in a `<tfoot>` element. Rows rendered inside opt out of the table's hover and striping styling.

PropTypeDefaultDescription
children*ReactNode—Footer row elements rendered inside the tfoot.
classNamestring—Additional CSS class names applied to the tfoot.
data-testidstring—Test ID applied to the tfoot.
refRef<HTMLTableSectionElement>—Ref forwarded to the tfoot element.
styleCSSProperties—Inline styles applied to the tfoot.

Wraps table header rows in a `<thead>` element. Rows rendered inside opt out of the table's hover and striping styling.

PropTypeDefaultDescription
children*ReactNode—Header row elements rendered inside the thead.
classNamestring—Additional CSS class names applied to the thead.
data-testidstring—Test ID applied to the thead.
refRef<HTMLTableSectionElement>—Ref forwarded to the thead element.
styleCSSProperties—Inline styles applied to the thead.

Table header cell with density and divider styling from context.

PropTypeDefaultDescription
aria-sortAriaAttributes['aria-sort']—Indicates whether and how the column is sorted.
childrenReactNode—Header cell content.
classNamestring—Additional CSS class names applied to the th.
data-column-keystring—Column key used by table plugins.
data-testidstring—Test ID applied to the th.
refRef<HTMLTableCellElement>—Ref forwarded to the th element.
scopeThHTMLAttributes<HTMLTableCellElement>['scope']'col'Scope of the header cell.
styleCSSProperties—Inline styles applied to the th.
titlestring—Native tooltip text applied to the th.

Table row with hover and striped styling from context. Header and footer rows are not interactive, so they render without that styling even when the surrounding table enables it.

PropTypeDefaultDescription
children*ReactNode—Cell elements rendered inside the row.
classNamestring—Additional CSS class names applied to the tr.
data-testidstring—Test ID applied to the tr.
refRef<HTMLTableRowElement>—Ref forwarded to the tr element.
styleCSSProperties—Inline styles applied to the tr.