UI/UX Atlas
Interaction Design Intermediate

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

Interactive example · Toasts & undo

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:

  1. Prevent — Make the wrong action impossible or unlikely, using constraints, smart defaults, and real-time validation.
  2. Undo — If a mistake happens, let the user reverse it easily with no consequences.
  3. 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-describedby so 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:

  1. Execute the action immediately (this is called an optimistic update — show the result before the server confirms it).
  2. Show a brief notification — typically 5–8 seconds — with an Undo button.
  3. If the user does nothing, commit the action when the timer expires.
  4. 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 and aria-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:

  1. What went wrong? Use specific, plain language. “Your session has expired” is better than “Error 401.”
  2. Why did it happen? Not always necessary, but context helps when the cause isn’t obvious.
  3. What should I do next? Give a concrete action, not a vague suggestion. “Sign in again” beats “Please try again.”
  4. 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:

CriterionRequirement
1.4.1 Use of ColorNever use color alone to indicate error; pair with an icon or text label
3.3.1 Error IdentificationErrors must be described in text
3.3.2 Labels or InstructionsInstructions sufficient to avoid errors must be available before input
3.3.3 Error SuggestionWhen an error is detected, suggest a correction unless it would compromise security
3.3.4 Error PreventionFor 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: transform and opacity. Animating border-color or background-color is fine but keep it brief. Never animate width, height, top, or left for 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-patternWhy it failsModern fix
Generic “Invalid input” errorUser doesn’t know what to fixSpecific: “Email must include @ and a domain, like [email protected]
Clearing form fields on errorForces re-entry of valid inputPreserve all values; highlight only invalid fields
Native browser confirm()Jarring, reflexively dismissed, inaccessibleCustom modal or undo toast scaled to consequence
Confirmation on every actionFatigue causes users to click throughReserve confirmation for irreversible, high-impact actions only
Inline validation on keypressFlags errors before user finishes typingValidate on blur; add success indicator after valid completion
Placeholder as labelDisappears on focus; low contrastPersistent top-aligned label; placeholder for example values only
”Try again” with no retry buttonUser must manually re-submitProvide a retry button; preserve form state
Error icon/color with no textFails WCAG 1.4.1 for color-blind usersAlways 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?