Bulk Actions
Let users perform one action across many records safely, with clear scope, eligibility and recovery.
9 states8 edge casesDesktop + Mobile
01The problem
Users need to perform the same operation across many records. Repeating it row by row is slow and error-prone, but acting on many records at once raises new questions: what exactly is selected, what happens to records the user can't act on, and how do they recover when part of the batch fails?
02When to use
Use when
- Approving, archiving or closing many invoices, tickets or requests
- Assigning records to an owner or team
- Applying or removing tags and labels
- Changing a status field across records
- Exporting a chosen subset of records
Avoid when
- Each record needs individual review before acting — use a review queue instead.
- The operation is irreversible and high-impact, such as permanently deleting customer data.
- Records almost never share an action; a per-row menu is simpler.
- The list rarely exceeds a handful of rows.
03Recommended UX
Statecraft recommendation
When the first row becomes selected, replace the table-level controls with a contextual selection toolbar showing the selected count and the actions available for that selection. Do not keep bulk actions permanently visible when nothing is selected.
Permanently visible bulk buttons are either disabled most of the time or act on an unclear scope. A toolbar that appears with the selection makes the scope explicit: these actions apply to these N records.
Swapping the toolbar in place — rather than adding a floating bar — keeps the action next to the data and avoids covering rows the user may still want to select.
The selected count is the single most important piece of copy in the pattern. It must be visible at all times while selection mode is active, including after scrolling.
04Interaction flow
- 01
Table at rest
Search, filters and view controls are visible.
- 02
Select first row
Checkbox, Shift-click for ranges, or Space on a focused row.
- 03
Selection mode
Toolbar swaps to count, actions and Clear.
- 04
Choose action
Ineligible records are counted before anything runs.
- 05
Confirm if destructive
Dialog states the exact count and consequence.
- 06
Processing
Rows lock; progress is shown for long batches.
Result
- Success
- Partial failure
- Failure
05Desktop behaviour
On desktop the table is the workspace. Selection happens inline through a leading checkbox column, and the toolbar above the table changes meaning when a selection exists.
Desktop rules
- Swap the table toolbar for the selection toolbar in the same position. Do not shift the table down.
- Show the 2–3 most frequent actions as buttons; put the rest in an overflow menu.
- Support Shift-click to select a contiguous range of rows.
- The header checkbox selects visible rows only. Offer “Select all 1,284 matching” as a separate, explicit step.
06Mobile behaviour
Mobile lists are rows of cards, not tables, and there is no hover or Shift key. Selection mode becomes an explicit mode the user enters and leaves.
Mobile rules
- Enter selection mode with a long-press or a “Select” button in the header — never show checkboxes on every card by default.
- In selection mode, tapping a card toggles it instead of opening it.
- Replace the header with the count and a Cancel control; place actions in a bottom bar within thumb reach.
- Limit the bottom bar to three actions plus overflow.
07States
Default
No selection. Table toolbar shows search, filters and view options.
Row hover
Desktop only. Row tints and the checkbox becomes more prominent to signal selectability.
Partially selected
Header checkbox shows the indeterminate state; toolbar shows “3 selected”.
All visible selected
Header checkbox checked. Offer to extend the selection to every matching record.
Action unavailable
Action shown but disabled, with a tooltip explaining why — e.g. “Paid invoices can’t be approved.”
Processing
Selected rows are locked, the toolbar shows progress, and other bulk actions are disabled.
Success
Toast confirms the count, selection clears and Undo is offered when reversible.
Partial failure
Successful records update; failed records stay selected with an inline reason per row.
Failure
Nothing changed. Selection is preserved so the user can retry without reselecting.
08Edge cases
EC-01
Selection + filters
The user selects 12 records, then changes the active filter so some selected records disappear from view.
Recommendation
Clear the selection when the result set changes and say so: “Selection cleared because the filter changed.” Acting on records the user can no longer see is the more dangerous failure.
EC-02
Selection across pages
The user selects rows on page 1, moves to page 2 and selects more.
Recommendation
Persist the selection across pages and always show the total count. Provide a “Show selected” view so users can audit records they can’t currently see.
EC-03
Select all matching
The user checks the header checkbox on a list of 1,284 filtered results with 50 visible.
Recommendation
Select the 50 visible rows only, then show an inline banner: “50 selected. Select all 1,284 matching invoices?” Never silently select records the user hasn’t seen.
EC-04
Mixed eligibility
20 rows are selected but only 15 can be approved; 5 are already paid.
Recommendation
Keep the action enabled and state the split before running: “Approve 15 invoices? 5 are already paid and will be skipped.” Disabling the button entirely leaves users guessing which rows are the problem.
EC-05
Mixed permissions
The user can edit some selected records but not others.
Recommendation
Treat it like eligibility for reversible actions — apply to permitted records and report the rest. For destructive actions, block and list the records the user lacks permission for.
EC-06
Partial failure
18 operations succeed and 2 fail due to a validation rule.
Recommendation
Report both numbers: “18 invoices approved. 2 couldn’t be approved.” Keep the 2 failed rows selected with an inline reason, so the next action is obvious.
EC-07
Record changed by someone else
Between selection and confirmation, a teammate approves one of the selected invoices.
Recommendation
Re-validate on the server at execution time. Report it as skipped, not failed: “1 invoice was already approved by Dana.”
EC-08
Very large batches
The user exports or updates 40,000 records.
Recommendation
Run the job in the background, return control immediately and notify on completion. Do not hold the user on a spinner for an operation measured in minutes.
09Microcopy
Selection count
Avoid
“Items selected”
Prefer
“3 invoices selected”
Destructive confirmation
Avoid
“Are you sure?”
Prefer
“Delete 24 customers? This can’t be undone.”
Partial failure
Avoid
“Something went wrong.”
Prefer
“18 invoices approved. 2 couldn’t be approved.”
Eligibility before running
Prefer
“Approve 15 invoices? 5 are already paid and will be skipped.”
Selection reset
Avoid
“Selection lost”
Prefer
“Selection cleared because the filter changed.”
Undo
Avoid
“Action completed”
Prefer
“12 invoices archived. Undo”
10Accessibility
- Selection must not rely on colour alone — the checkbox state is the source of truth, row tint is secondary.
- Every row checkbox needs an accessible name that identifies the record, e.g. “Select invoice INV-1024”.
- The header checkbox must expose the mixed state via aria-checked="mixed" when partially selected.
- Announce selection count changes through a polite live region; do not announce every individual toggle.
- When the selection toolbar replaces the table toolbar, keep keyboard focus on the row the user was on.
- Escape should clear the selection and exit selection mode on desktop.
11Developer notes
- 01
Store selection as a set of stable record IDs, not row indexes or object references, so it survives sorting, pagination and refetching.
- 02
Model “all matching” as a query plus an exclusion list, not as an enumerated list of IDs. This keeps 40,000-record selections cheap and correct.
- 03
Bulk endpoints should return per-record results ({ id, status, reason }) rather than a single success boolean. The partial-failure UI depends on it.
- 04
Re-check eligibility and permissions server-side at execution time; the client view may be stale.
- 05
Make bulk operations idempotent so a retry after a timeout can’t double-apply.
12Implementation prompt
For Claude Code, Codex or Cursor
Implement a production-quality bulk selection system for the existing data table. Requirements: - Individual row selection via a leading checkbox column. - Header checkbox selects visible rows only, with an indeterminate state when partially selected. - After selecting all visible rows, offer "Select all N matching" as an explicit second step. - Shift-click selects a contiguous range. - Store selection as a Set of stable record IDs; persist it across pagination and sorting. - Clear the selection when filters or search change, and show a short notice explaining why. - When at least one row is selected, replace the table toolbar with a contextual selection toolbar: selected count, primary actions, overflow menu, Clear selection. - Before running an action, compute eligible vs. ineligible records and show the split in the confirmation. - Confirmation dialog for destructive actions, stating the exact count and consequence. - Processing state: lock selected rows and disable other bulk actions. - Handle per-record results from the API: success, skipped, failed (with reason). - On partial failure, keep failed rows selected and show the reason inline. - Offer Undo for reversible actions. - Keyboard: Space toggles focused row, Escape clears selection. Announce count changes via a polite live region. - On mobile, use an explicit selection mode with a bottom action bar instead of checkboxes on every card. Do not redesign the existing interface. Keep interaction logic in a reusable hook, separate from presentation.
React + TypeScript reference code for this pattern — logic, hook, component and tests — is included in Statecraft Pro.