React table row selection & bulk actions
▶ Try it live: open a Mantine starter in StackBlitz — a real AdaptTable you can edit in the browser, no install. Other UI kits →
Import bulkActions from @adapttable/<kit>/bulk-actions and compose it in
features — checkboxes on every row, a tri-state header checkbox, and a
selection toolbar with your action buttons. The import and features entry
are the switch; there is no bulkActions table prop. See
feature composition.
Example
Section titled “Example”// or import from "@adapttable/mui", "@adapttable/chakra", "@adapttable/antd",// "@adapttable/radix", "@adapttable/shadcn", "@adapttable/unstyled" — same props everywhere.import { type BulkAction, DataTable, type RowAction,} from "@adapttable/mantine";import { bulkActions } from "@adapttable/mantine/bulk-actions";import { rowActions } from "@adapttable/mantine/row-actions";
interface Person { id: string; name: string; role: string; status: string;}
const PEOPLE: Person[] = [ { id: "1", name: "Ada Lovelace", role: "Engineer", status: "active" }, { id: "2", name: "Alan Turing", role: "Founder", status: "active" }, { id: "3", name: "Grace Hopper", role: "Admiral", status: "retired" },];
// Per-row: trailing buttons on each desktop row / mobile card.const rowActionDefs: RowAction<Person>[] = [ { key: "edit", label: "Edit", onClick: (row) => console.log("edit", row.id) },];
// Bulk: buttons in the selection toolbar, fired with the selected ids.const bulkActionDefs: BulkAction[] = [ { key: "archive", label: "Archive", onClick: (ids, { allMatching, total }) => { if (allMatching) console.log(`archive all ${total} matching rows`); else console.log("archive", ids); }, }, { key: "delete", label: "Delete", color: "red", confirm: { title: "Delete people", message: (count) => `Delete ${count} people? This cannot be undone.`, confirmLabel: "Delete", danger: true, }, onClick: (ids) => console.log("delete", ids), },];
export function PeopleTable() { return ( <DataTable data={PEOPLE} columns={[ { key: "name", sortable: true }, { key: "role" }, { key: "status" }, ]} rowKey={(r) => r.id} features={[rowActions(rowActionDefs), bulkActions(bulkActionDefs)]} onSelectionChange={(ids) => console.log("selected", ids)} /> );}How it works
Section titled “How it works”bulkActions(actions)is the switch: composing it enables the checkbox column, the header tri-state (all / some / none of the visible rows), and the bulk bar that appears once at least one row is selected.RowActionvsBulkAction: a row action runs on one row (onClick(row),confirm.message(row)); a bulk action runs on the selection (onClick(ids, context),confirm.message(count)).- Select all on the page vs all N matching: the header checkbox selects
the visible page. When the whole page is selected and more rows match, a
Gmail-style banner offers “Select all N matching”; accepting widens the
scope and your action receives
BulkActionContext—{ allMatching: true, total }— so you act on the whole filtered set server-side, not just the pageids. Any explicit toggle narrows the scope back to concrete ids. - Confirmation sized by scope: a bulk action’s
confirm.message(count)receivescontext.totalwhen all-matching is active, the page ids count otherwise. The dialog goes through the table’sconfirmhandler (window.confirmby default);danger: truemarks it destructive. - Controlled or uncontrolled: omit
selectedIdsand the table owns the selection (onSelectionChangeis then an observer). PassselectedIdsand it becomes controlled — applyonSelectionChangerequests to your state to accept them, the same split ascolumnLayout. - Selection is keyed by id (
selectionGetId, defaulting torowKey), so it survives page, sort, and page-size changes — and resets automatically when the result set changes (a new search term or different filter values).
Options
Section titled “Options”| Factory / prop | Type | Default | Description |
|---|---|---|---|
bulkActions(actions) |
BulkAction[] |
— | Factory from @adapttable/<kit>/bulk-actions; composing it turns on row selection and mounts the bulk bar with these buttons. |
rowActions(actions) |
RowAction<TRow>[] |
— | Factory from @adapttable/<kit>/row-actions; trailing per-row actions, independent of selection. |
rowActionsLayout |
RowActionsLayout |
"buttons" |
Omit or "buttons" for the strip; "menu" for a 3-dot menu. |
renderRowActions |
RowActionsRenderer<TRow> |
— | Replace the trailing actions cell; wins over rowActionsLayout. |
selectedIds |
readonly string[] |
— (uncontrolled) | Controlled selection ids. |
onSelectionChange |
(ids: string[]) => void |
— | Uncontrolled: observer for every change — once on mount with the (empty) initial set, per toggle/select-all, and on the automatic reset when search or a filter changes. Controlled: the change-request handler (no mount fire). |
selectionGetId |
(row: TRow) => string |
rowKey |
Selection id extractor when it must differ from the React key. |
confirm |
ConfirmHandler |
window.confirm |
Confirmation handler for actions with a confirm block; pass your own for a styled dialog. |
BulkAction.onClick(ids, context)may return a promise: the button shows a loading state, other bulk buttons disable while it runs, and the selection clears after a successful run.BulkAction.disabledReason(ids)returns a non-empty string to grey the button out and explain why (shown as its tooltip). Row actions havedisabledReason(row),isDisabled(row), andisHidden(row).- The per-action
confirmblock is{ title, message, confirmLabel, danger? }— all strings pre-translated. Yourconfirmhandler receives the fullConfirmRequest(includingcancelLabelandonConfirm). - The selection toolbar’s strings (
selectedCount,selectAllMatching,allMatchingSelected, …) are overridable via thelabelsprop. - Headless consumers can reuse the same machinery:
useSelection(with aresetKey),useBulkActionRunner, andrunRowActionare exported from@adapttable/core.
See it live in the demo.