Acronis UIKit
Components

Data table bulk actions

Select rows in a data table and act on the selection via a contextual bulk-action bar.

The approved way to offer row selection with bulk actions. A checkbox selection column drives DataTableBulkActionsBar — a persistent bar above the grid whose children are your bulk actions (Export, Delete, …), with a trailing selection summary and a Deselect control.

The activation threshold belongs to the components, not to you: isBulkSelectionActive(table)one or more rows selected, including exactly one. The bar is always mounted; rather than appearing and disappearing, it switches between two states on that predicate:

  • Idle (nothing selected) — the actions are disabled through a native <fieldset disabled>, and the trailing side shows your loadedLabel (e.g. "25 of 1250 items loaded") instead of a selection summary.
  • Active (one or more rows selected) — the actions enable and the trailing side shows the selection summary plus Deselect.

TableActionsCell takes the same flag as bulkSelectionActive to blank out each row's trigger while the bulk scope is active.

Because the bulk bar and the row checkboxes must read the same selection state, that state needs one source of truth. This page composes the Table primitives + DataTablePagination over a single useReactTable instance. The self-contained DataTable is equally valid: it keeps its own internal instance, but its controlled rowSelection / onRowSelectionChange props let you lift the selection into your own state and drive a minimal second instance for the bar off the same state — the shape of the CoreCapabilities / CoreCapabilitiesWithPagination stories.

When to use

  • A table where users pick several rows and apply one action to all of them.
  • Destructive or batch operations (delete, export, assign) across rows.

When not to use

  • A read-only or single-select table — use DataTable directly.
  • One action per row — put a row action (menu / button) in a cell instead.

Anti-patterns

  • Leaving DataTable's selection uncontrolled while a separate useReactTable drives the bar — two independent rowSelection states that diverge. Either build one shared instance, or lift a single rowSelection state and pass it to both (DataTable's rowSelection / onRowSelectionChange plus the bar's instance).
  • Mounting the bar conditionally so it appears and disappears — it is built to stay put and toggle its own state. A mount/unmount reflows the page on every first and last selection.
  • Omitting loadedLabel when the idle state has something useful to say (e.g. "25 of 1250 items loaded") — the trailing area then sits empty until the first selection.
  • Re-deriving the condition in your own code instead of calling isBulkSelectionActive(table) — the predicate is exported so the bar and every TableActionsCell can't drift apart.
  • Hiding the selection count — always show "N items selected" so the scope is clear.

How it works

const [rowSelection, setRowSelection] = useState({});
const table = useReactTable({
  data,
  columns, // includes a checkbox 'select' column
  getCoreRowModel: getCoreRowModel(),
  getPaginationRowModel: getPaginationRowModel(),
  enableRowSelection: true,
  onRowSelectionChange: setRowSelection,
  state: { rowSelection },
});
const bulkSelectionActive = isBulkSelectionActive(table);

<DataTableBulkActionsBar
  table={table}
  loadedLabel={`${data.length} items total`}
>
  <Button variant="secondary">Export</Button>
  <Button variant="destructive">Delete</Button>
</DataTableBulkActionsBar>

Mount the bar unconditionally, then render the grid from table.getHeaderGroups() / getRowModel() — passing bulkSelectionActive to each row's TableActionsCell — and pass the same table to DataTablePagination. The bar reads the count off table.getSelectedRowModel(), disables its own actions while nothing is selected, and wires Deselect to table.resetRowSelection() itself — it owns no state.

Its strings are localizable: selectedLabel (a (count: number) => string, default (count) => `${count} item${count === 1 ? '' : 's'} selected:` ), clearLabel (default "Deselect"), and loadedLabel (no default — omit it and the idle state's trailing area stays empty).

Edit on GitHub

On this page