Error Prevention, Undo & Recovery Design
Designing interfaces that stop mistakes before they happen — and gracefully recover when they do — is what separates safe, trustworthy software from frustrating ones.
11 min read
Toasts appear here
Confirmations are non-blocking and auto-dismiss; destructive actions pair with an undo window instead of a confirm dialog — recovery over prevention.
The full lesson
Errors will happen. What separates a great interface from a frustrating one is how well it prevents mistakes, lets users reverse them, and helps them recover when something goes wrong. Nielsen’s heuristics #5 (error prevention) and #9 (help users recover from errors) are two of the most violated in real products — and two of the highest-return areas a design team can improve.
This lesson covers the three-layer model: prevent errors first, offer undo as a safety net, and guide recovery as a last resort. Each layer has its own patterns, accessibility requirements, and anti-patterns to avoid.
The Three-Layer Model
Think of error-tolerant design as a hierarchy. Put your energy at the top:
- Prevent — Make the wrong action impossible or unlikely, using constraints, smart defaults, and real-time validation.
- Undo — If a mistake happens, let the user reverse it easily with no consequences.
- Recover — When an error state is reached, explain exactly what went wrong and give a clear next step.
The most common mistake is jumping straight to “write a good error message.” Error messages are the failure mode of prevention — they mean the first two layers weren’t enough.
Layer 1 — Error Prevention
Constraints and affordances
The most powerful prevention technique is making the wrong action impossible. A date-picker that disables past dates wipes out an entire class of scheduling errors. A file-upload field that only accepts .csv files blocks the wrong format before the server ever sees it.
Good constraints share three qualities:
- Visible — the user can see which options are unavailable, and ideally why.
- Non-destructive — disabling a button is better than hiding it entirely; the user should know the option exists.
- Paired with a reason — “Dates in the past are unavailable” communicates intent; a greyed-out button with no label does not.
Smart defaults
Pre-filling fields with the most likely value cuts user error significantly. Research on form completion shows pre-populated fields reduce both error rate and time to complete — but defaults must be honest. Pre-checking a marketing opt-in box is a deceptive pattern. It’s now legally actionable under GDPR and increasingly under US state law. A good default represents what most users actually want, not what the business prefers.
Inline validation on blur
Validate a field when the user leaves it (the “blur” event), not on every keystroke and not only on form submit. Keystroke-level validation is jarring — it fires “invalid email” before the user finishes typing. Submit-only validation forces the user to hunt for errors after filling the entire form.
Modern best practice:
- Validate on blur for most fields.
- Show a success indicator (a checkmark or green border) when the user gets a field right — this reassurance reduces unnecessary re-editing.
- For password strength, a live meter while typing is fine because it’s purely additive, not an error.
- Connect error messages to their fields with
aria-describedbyso screen-reader users hear the error when they focus the field. Never rely on color alone (WCAG 2.2 criterion 1.4.1).
Confirmation for destructive actions
For irreversible, high-stakes actions — deleting an account, sending a payment, publishing to production — add a confirmation step. How you design that step matters a lot:
- Scale friction to consequence. Deleting a draft note? A brief undo toast with a timer is enough. Permanently deleting 10,000 records? Require the user to type a phrase like “delete my account.” This technique, popularized by GitHub for repository deletion, forces deliberate intent and virtually eliminates accidental execution.
- Don’t use native browser
confirm()dialogs. They are visually jarring, unstyled, not accessible, and users have been trained to dismiss them reflexively. Use a purpose-built modal or inline confirmation pattern instead. - Don’t confirm everything. Confirmation fatigue is real. If every action triggers a modal, users click “OK” without reading. Reserve confirmation for actions that are genuinely irreversible and high-impact.
Do
Use a typed-confirmation field (“Type DELETE to confirm”) for permanently destructive, irreversible bulk actions. Scale friction to consequence — a low-stakes action deserves only a brief undo toast, not a modal.
Don't
Show a native browser confirm() dialog, or trigger confirmation modals for routine reversible actions. Both patterns cause users to click through without reading, defeating the purpose.
Layer 2 — Undo
Undo is the most underused safety mechanism in product design. Done well, it removes the need for confirmation dialogs on most non-critical actions, making the interface feel faster and less paternalistic.
The undo toast pattern
For actions that are reversible within a short window, the undo toast is the gold standard. Here’s how it works:
- Execute the action immediately (this is called an optimistic update — show the result before the server confirms it).
- Show a brief notification — typically 5–8 seconds — with an Undo button.
- If the user does nothing, commit the action when the timer expires.
- If the user taps Undo, reverse the action and dismiss the toast.
Gmail’s “Message sent. Undo” is the classic example. Users get a fast, friction-free experience plus a safety net. The timer should be long enough to read and decide (5 seconds minimum) but short enough not to interrupt the user’s flow.
Accessibility requirements for undo toasts:
- Announce the toast to screen readers via an ARIA live region — use
aria-live="polite"for low-priority notifications andaria-live="assertive"for errors. - The Undo button must be keyboard-accessible with a visible focus indicator that meets WCAG 2.2’s focus-not-obscured criterion (2.4.11).
- The toast must not auto-dismiss while it has keyboard focus (WCAG 2.2 criterion 2.2.1 allows user-controlled timing).
Multi-level undo
Desktop applications — image editors, word processors, IDEs — use a command stack: each action is a reversible object pushed onto a stack, so Ctrl+Z steps back through history. Complex web apps (Figma, Notion, linear project boards) now follow the same pattern.
For web apps with undo stacks, keep history bounded (50–100 actions is reasonable) and consider persisting it across sessions for long-lived documents. Losing undo history on a browser refresh is a major pain point.
What cannot be undone
Some actions are genuinely irreversible: sending an email through an external server, executing a payment, posting to a social network via API. When undo is truly impossible, prevention becomes the only safety net — the confirmation step before the action matters even more. Communicate the irreversibility clearly before the action, not after.
Layer 3 — Error Recovery
When an error state is reached, your job is to help the user understand what happened and take a clear next step. Most production error messages fail at both.
Anatomy of a good error message
A well-written error message answers four questions:
- What went wrong? Use specific, plain language. “Your session has expired” is better than “Error 401.”
- Why did it happen? Not always necessary, but context helps when the cause isn’t obvious.
- What should I do next? Give a concrete action, not a vague suggestion. “Sign in again” beats “Please try again.”
- Can I keep what I wrote? Always preserve the user’s input. Clearing all fields on a validation failure is one of the worst mistakes in form design.
Error message placement
Place error messages next to the field or action that caused them. A summary at the top of the form can supplement field-level messages — this is especially helpful on long forms where inline errors might be off-screen — but it should never replace them.
The summary should:
- Move focus to the summary when the form fails on submit (programmatically, with JavaScript
focus()). - List each error as a link that moves focus to the relevant field when clicked.
- Use a heading like “3 errors found” so screen-reader users immediately understand the scope.
Error message tone
Keep errors clear and neutral. Blame-assigning language (“You entered an invalid email”) is worse than system-focused language (“This doesn’t look like a valid email address”). Skip exclamation marks and over-apologies like “We’re so sorry!” — both are noise.
Avoid jargon. “400 Bad Request,” “CORS policy violation,” and “NullPointerException” belong in browser DevTools, not in your UI. Always translate technical errors into plain language before surfacing them to users.
Empty states with recovery paths
The “No results” state is an error state that most teams treat as an afterthought. A blank page with no guidance leaves users stuck. A good empty state does three things:
- Confirms what was searched for: “No results for ‘invoics’.”
- Suggests a recovery action: “Check for typos” or “Try broader terms.”
- Offers an escape hatch: “Browse all products” or “Clear filters.”
This applies equally to empty lists after filtering, empty inboxes, and onboarding states where the user hasn’t created any content yet.
System errors and downtime
Network failures, server errors, and partial outages need special care:
- Distinguish the user’s fault from the system’s fault. A 404 (resource not found) is different from a 500 (server error). The former may be the user’s mistake; the latter is yours — take ownership in the message.
- Preserve context. If a form submission fails due to a network error, keep the user’s input so they can retry without re-entering everything.
- Give a retry path. A “Retry” button with exponential back-off (waiting a bit longer each time before retrying) is better than asking the user to refresh the page.
- Show status, not just failure. A link to a status page or an estimated resolution time reduces support load and builds trust.
Accessibility Considerations
Error states must meet WCAG 2.2 requirements across all three layers:
| Criterion | Requirement |
|---|---|
| 1.4.1 Use of Color | Never use color alone to indicate error; pair with an icon or text label |
| 3.3.1 Error Identification | Errors must be described in text |
| 3.3.2 Labels or Instructions | Instructions sufficient to avoid errors must be available before input |
| 3.3.3 Error Suggestion | When an error is detected, suggest a correction unless it would compromise security |
| 3.3.4 Error Prevention | For legal, financial, or data-deletion actions, at least one of: reversible, checked, or confirmed |
| 2.4.11 Focus Not Obscured (2.2) | Error toasts and sticky headers must not fully obscure the focused element |
| 2.5.8 Target Size Minimum (2.2) | Undo and confirmation buttons must meet the 24x24 CSS pixel minimum target size |
Criterion 3.3.4 encodes the three-layer model into law for high-stakes actions. If your product handles financial transactions, legal agreements, or user-data deletion, this criterion is non-negotiable.
Motion and Animation in Error States
Error feedback often uses animation — a shaking form field, a bouncing icon, a flashing highlight. Follow these principles:
- Animate only compositor-friendly properties:
transformandopacity. Animatingborder-colororbackground-coloris fine but keep it brief. Never animatewidth,height,top, orleftfor error feedback. - Use spring-based or ease-out easing, not linear. A shake animation with linear easing feels mechanical; one with slight overshoot feels alive and intentional.
- Always respect
prefers-reduced-motion. The error state must still be clear without animation — rely on color, icon, and text, not motion alone. - Keep error animations short: 200–400 ms. Longer animations feel punishing.
Common Anti-Patterns
| Anti-pattern | Why it fails | Modern fix |
|---|---|---|
| Generic “Invalid input” error | User doesn’t know what to fix | Specific: “Email must include @ and a domain, like [email protected]” |
| Clearing form fields on error | Forces re-entry of valid input | Preserve all values; highlight only invalid fields |
Native browser confirm() | Jarring, reflexively dismissed, inaccessible | Custom modal or undo toast scaled to consequence |
| Confirmation on every action | Fatigue causes users to click through | Reserve confirmation for irreversible, high-impact actions only |
| Inline validation on keypress | Flags errors before user finishes typing | Validate on blur; add success indicator after valid completion |
| Placeholder as label | Disappears on focus; low contrast | Persistent top-aligned label; placeholder for example values only |
| ”Try again” with no retry button | User must manually re-submit | Provide a retry button; preserve form state |
| Error icon/color with no text | Fails WCAG 1.4.1 for color-blind users | Always pair color with a text label or icon plus label |
Do
Write error messages that name the specific field, describe what the problem is, and tell the user exactly what a valid value looks like. Connect the message to its field with aria-describedby so assistive technology announces it automatically when the user moves focus to the field.
Don't
Show a generic “Please check your form” message at the top of the page with no field-level detail, or use color alone (a red border) to indicate which fields are invalid. Both approaches leave users guessing.
Putting It Together: A Practical Checklist
Before shipping any user-facing form or destructive action, run through these questions:
- Can the wrong action be made impossible or unlikely through constraints or smart defaults?
- Is validation triggered on blur, with specific messages connected via
aria-describedby? - For destructive actions: is the consequence scaled to the risk level? Would an undo toast suffice, or is explicit confirmation required?
- Are error messages in plain language, specific, and action-oriented?
- Is user input preserved after a validation failure or network error?
- Do empty and error states include a recovery path (a button, a suggestion, an escape hatch)?
- Do all error animations respect
prefers-reduced-motion? - Are error states communicated without relying on color alone?