DataTable

FigmaGithubStorybook

DataTable enables users to act upon complex datasets with a variety of features including filtering and sorting.

DataTable

Usage

  • Use DataTable to display, organize, compare, and act on tabular data with multiple fields.
  • Use DataTable for simple comparative lists too — omit interactive features (search, filters, selection) when the data does not need them.
  • Never use DataTable, or any table, for page layout. Use Box or Grid instead.
  • Enable only the features the user needs. Too many options overwhelm the user.
  • Keep the number of visible columns low. Expose the rest through column management (withColumnsSettings) rather than showing them by default.

Types

  • Basic — a non-interactive table for display and comparison. Pass only data, columns, and caption.
  • Interactive — a table with user-driven workflows. Add features such as withRowSelection, withSearch, withSorting, withFilters, withPagination, and bulkActions.

Appearance

Layout

  • layout="fixed" — the default. Columns divide the available width equally regardless of content, and horizontal scrolling is disabled.
  • layout="auto" — columns size to their content. Use when cell content must stay readable.
  • Horizontal scrolling is available only with layout="auto".

Column width

  • Set a column's width with meta.width on its column definition. Accepts pixels ('200px'), a number, a percentage ('50%'), or a fractional unit ('2fr') that divides the remaining space.
  • Constrain a column with meta.minWidth and meta.maxWidth.
  • Align cell content with meta.align and meta.bodyCellVerticalAlign.

Row styles

  • Striped (isStriped) — alternates row backgrounds. Use for large datasets, especially with infinite scrolling.
  • Borderless (isBorderless) — removes row borders. Use for a cleaner look on small datasets.
  • Hover highlight (shouldHighlightOnHover) — highlights the row under the pointer. Applied automatically to selectable rows; set it explicitly on non-selectable rows.

Behaviors

States

  • Loading (isLoading) — renders skeleton rows. Set loadingState.rowCount for the number of placeholder rows.
  • Empty (withEmptyState, default true) — renders when data is empty. Configure title, description, icon, and actions through emptyState.
  • Selected — selected rows take a highlighted background and a checked checkbox when withRowSelection is on.

Sorting

  • Enable with withSorting. Users sort from the column header or the Sort menu in the header.
  • Control it with sorting.value and sorting.onChange. Set sorting.isManual for server-side sorting.
  • Sorting is single-column only, and a sorted column cannot be returned to its unsorted state.
  • Enable with withSearch. Renders a search input in the header that filters across all columns.
  • Control it with search.value, search.onSearch, and search.onSearchInput. Set search.isManual for server-side search, or pass search.globalFilterFn for a custom client-side matcher.
  • Collapse the input behind a button with search.defaultIsExpanded={false}.

Filters

  • Enable with withFilters and pass a filters config holding the filter definitions, active values, and change handlers.
  • Filters render in the header and combine to narrow results.

Pagination

  • Enable with withPagination. Renders page controls below the table.
  • Configure page size and behavior through pagination; control it with pagination.page and pagination.onPageChange. Set pagination.isManual when data arrives already paginated.

Infinite scrolling

  • Enable with withVirtualizer to render only visible rows for large datasets. Use it as the alternative to pagination, not alongside it.
  • Give the table a height-constrained container so it scrolls.
  • Tune it with virtualizerOptions.

Row selection

  • Enable with withRowSelectiontrue for every row, or a function to decide per row.
  • Control it with rowSelection and onRowSelectionChange.
  • Restrict to one row at a time with enableMultiRowSelection={false}.
  • Block selection on specific rows with getRowCanSelect.

Bulk actions

  • Configure bulkActions alongside withRowSelection. The bar appears in the header once rows are selected.
  • promotedActions render as buttons; dropdownActions render inside a dropdown.

Row actions

  • Pass rowActions as a config object, or a function receiving the row. Renders a dropdown in a trailing cell on each row.
  • dropdownActions takes either action items or grouped actions.

Row expansion

  • Enable with withRowExpanding to add an expander toggle column.
  • Rows expand to their sub-rows, read from a children array by default. Override the accessor with getSubRows.
  • Restrict which rows expand with getRowCanExpand, pre-expand rows with initiallyExpandedRows, and react to changes with onRowExpansionChange.

Column management

  • Enable with withColumnsSettings to add a Columns button in the header for toggling visibility.
  • Set initially hidden columns with defaultColumnVisibility; control visibility with columnVisibility and onColumnVisibilityChange.
  • Set column order with defaultColumnOrder, or control it with columnOrder and onColumnOrderChange.

Column span

  • Span a cell across neighboring columns with meta.getCellColSpan on the column definition.

Saved views

  • Enable with withSavedViews and configure the view list, creation, editing, and deletion through savedViews.

Load new / load more

  • Use loadNew to surface a row at the top of the table when new data is available.
  • Use loadMore to surface a row at the bottom for appending the next batch.

Content

  • Give every table a caption — a short, descriptive noun phrase. It is required and read by assistive technology, but not visible.
  • Write column headers in sentence case, using full words rather than abbreviations.
  • Write a meaningful empty state with a title, a description, and an action where one applies. Never leave the table blank.

Implementation Notes

  • data, columns, and caption are required. Build columns with createColumnHelper from @tanstack/table-core — they are TanStack Table column definitions.
  • Customize cell content with the cell function on a column definition. Use it for formatting, badges, links, or any component in place of the raw value.
  • Customize the empty state with emptyStateComponent, which replaces the whole emptyState block. React takes a component receiving table; Ember takes a component with a @table arg.
  • Customize expanded rows with expandedRowComponent, which replaces sub-row rendering under withRowExpanding. React takes a component receiving row; Ember takes a component with a @row arg. Pair it with getRowCanExpand.
  • Pass children (Ember: the default block) to replace the entire default composition — Header, Table, and Pagination — with your own arrangement of DataTableHeader, DataTableTable, and DataTablePagination.
  • Row IDs come from getRowId, then the id property on each row, then the row index. Override with getRowId whenever rows have a stable key.
  • Cell content truncates with an ellipsis by default. Set meta.shouldWrapCellContent on a column to wrap instead.
  • Set meta.name when a column's header is not a plain string — the columns menu and sort menu need a readable label. Use meta.loadingPlaceholder for a column-specific skeleton and meta.sortDirectionLabelType to switch the sort labels to text.
  • meta.minWidth and meta.maxWidth accept numbers, px, and % only — fr units are rejected. meta.width accepts fr as well.
  • Selection and expander columns are always ordered first, and the row-actions column always last, regardless of columnOrder. These private columns are stripped from onColumnOrderChange and onColumnVisibilityChange payloads.
  • Clicking anywhere in a row toggles its expansion, or its selection when the row is selectable but not expandable. Rows that are both selectable and expandable are not able to be click-toggled — the checkbox and expander handle it.
  • The design doc calls the default layout fixed and the BaseTableProps JSDoc says auto — the component's actual default is layout="fixed".
  • Deprecated props — use sorting.onChange instead of the top-level onSortingChange, and sorting.value (a { id, desc } object inside the sorting config) instead of passing { id, desc } as sorting itself.
  • Extends Box — sprinkle props (spacing, layout) are supported, and as overrides the root element (default div).
  • Ember invocation: <PlumaDataTable @data={{this.data}} @columns={{this.columns}} @caption="Items" @withSearch={{true}} />. Arg names and defaults match React; config objects (sorting, search, pagination, filters) take the same shape.

On this page