Error Summaries for Batch Operation Failures
Permalink to "Error Summaries for Batch Operation Failures"Bulk operations rarely fail completely; they fail partially. An import of 12,400 rows adds 12,397 and rejects three. A bulk status change succeeds for 48 invoices and fails for two that are locked. The worst response is a generic “Some items could not be processed” toast. Users — especially screen reader users, who cannot scan a grid for red rows — need to know how many failed, which ones, why, and what they can do about it. That is an error summary, the same pattern form validation has used for years, applied to batch operations.
This guide belongs to notifications & toasts for data operations.
Spec reference
Permalink to "Spec reference"- SC 3.3.1 Error Identification (A) — failed items must be identified and the error described in text. For batch operations, that means each failed row and its reason.
- SC 3.3.3 Error Suggestion (AA) — where a fix is known (“Date must be YYYY-MM-DD”), say so.
- SC 4.1.3 Status Messages (AA) — the outcome counts are a status message if focus does not move; if the user is waiting for the result, moving focus to the summary is better.
- SC 2.4.4 Link Purpose (A) — links to failed rows must say which row.
- SC 1.3.1 Info and Relationships (A) — the summary is a list with a heading.
When to use — and when not to
Permalink to "When to use — and when not to"Use an error summary whenever a batch operation can partly fail and users need to act on the failures — imports, bulk edits, bulk deletions with locks, synchronisation with external systems.
Do not use a summary for single-item failures; show those in context. Do not list thousands of failures inline: show the first 20 with the total and offer a download of the full list.
The misapplication: marking failed rows in the grid with a red background and a toast saying “3 rows failed”. Screen reader users would have to visit every row to find them.
Annotated code example
Permalink to "Annotated code example"<section class="error-summary" aria-labelledby="import-result">
<!-- Focus target when the user waited for the result -->
<h2 id="import-result" tabindex="-1">Import finished: 12,397 added, 3 rejected</h2>
<p>Fix the rows below and retry, or download them to edit in a spreadsheet.</p>
<ul>
<!-- Each failure: which row, which field, what to do (SC 3.3.1, 3.3.3, 2.4.4) -->
<li><a href="/imports/88/rows/18">Row 18, INV-0218</a>: Due date “31/02/2026” is not a date. Use YYYY-MM-DD.</li>
<li><a href="/imports/88/rows/402">Row 402, INV-0602</a>: Amount −40.00 is negative. Amounts must be 0 or more.</li>
<li><a href="/imports/88/rows/9113">Row 9,113, INV-9313</a>: Customer ID C-4471 does not exist.</li>
</ul>
<button type="button">Retry 3 rejected rows</button>
<a href="/imports/88/rejected.csv" download>Download 3 rejected rows (CSV)</a>
</section>
function showBatchResult(result, { userWaiting }) {
render(summaryFor(result));
if (userWaiting) {
document.getElementById('import-result').focus(); // user is waiting: move to the answer
} else {
notify({ text: `Import finished: ${result.ok} added, ${result.failed} rejected.`,
kind: result.failed ? 'warning' : 'info',
action: { label: 'View rejected rows', run: openSummary } }); // background: toast + history
}
}
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Event | Expected announcement | AT deviations |
|---|---|---|
| Result while user waits | Focus to heading: “Import finished: 12,397 added, 3 rejected, heading level 2” | JAWS may continue reading the paragraph |
| Result in background | Toast: “Import finished… 3 rejected.” with View action | — |
| Tab through failures | “Row 18, INV-0218, link” then reason text | Reason read after the link in browse mode |
| Retry rejected | “Retrying 3 rows” then a new summary | — |
Integration context
Permalink to "Integration context"The summary is the destination of toasts from accessible toast notifications for background jobs. Per-cell errors inside an editable grid use inline form validation inside editable table cells. Bulk operations started from a selection come from contextual bulk action toolbars, whose announcements should hand off to this summary.
Gotchas
Permalink to "Gotchas"Row numbers that change. “Row 18” in the uploaded file is not row 18 in the grid. Use the file’s row number and the record’s identifier together.
Reasons in codes. “E_FK_VIOLATION” is not a reason. Map errors to sentences users can act on.
Selection after partial failure. For bulk actions on a selection, keep the failed rows selected and deselect the succeeded ones, then say so: “2 invoices could not be archived and are still selected.”
Framework notes
Permalink to "Framework notes"Server responses should return per-item results, not a single status. A common shape is { succeeded: [...ids], failed: [{ id, row, field, code, message }] }. In React with TanStack Query, render the summary from the mutation’s data in onSuccess, even when some items failed — partial failure is a successful response with failures in it, not an exception. In Angular, map the HTTP 207 Multi-Status responses some APIs use to the same shape.
// Partial failure is data, not an error
const { mutate } = useMutation({ mutationFn: bulkArchive, onSuccess: (res) => showBatchResult(res, { userWaiting: true }) });
Worked example
Permalink to "Worked example"A product manager imports 2,000 SKUs from a supplier spreadsheet. Seven rows fail. Previously, the grid highlighted them in pink and a toast said “Import completed with errors.” Now, because she is waiting on the import screen, focus moves to a heading — “Import finished: 1,993 added, 7 rejected” — followed by a list where each item reads like “Row 112, SKU AB-2291: Price ‘12,50’ uses a comma. Use a full stop for decimals.” She downloads the seven rows, fixes them in her spreadsheet and uses “Retry 7 rejected rows”. The second summary reads “Import finished: 7 added, 0 rejected”, and the rows she fixed are now in the grid.
Testing checklist
Permalink to "Testing checklist"Design system notes
Permalink to "Design system notes"Add a BatchResult component that takes the per-item result shape and renders the summary, links and follow-up actions, plus a matching toast template. Document the error message style guide — row, field, problem, fix — next to it.
FAQ
Permalink to "FAQ"Should failures be announced one by one?
No. Announce or focus the counts; users then read the list at their own pace.
Should focus always move to the summary?
Only when the user is waiting for the result on the same page. If they have moved on, a toast with a link is less disruptive.
How many failures should be listed inline?
Up to about 20. Beyond that, show the count, the first few, and a download of the full list.
Is partial success a success or an error?
Both: report it as a warning-level outcome with counts. Treating it as a plain success hides the failures; treating it as an error hides the work that succeeded.
Related
Permalink to "Related"- Toasts for background jobs — the hand-off when users have moved on
- Contextual bulk action toolbars — where bulk actions start
- Writing actionable accessibility bug reports — the same clarity in a different context