Back to Articles

Error Message Design: Writing Copy That Actually Helps Users Recover

Error Message Design: Writing Copy That Actually Helps Users Recover

Teams ship tidy screens and shrug at error copy. That creates support tickets and abandoned tasks.

The standard approach to error messages is to write them last, treat them as edge cases, and hand them off to whoever gets to them first — a developer, a PM, someone with fifteen minutes before launch. The result is "An error occurred. Please try again." Technically correct. Completely useless.

Error states are not edge cases. They are a primary interface. For a significant slice of your users, the error message is the product. It is the moment that determines whether they recover and continue, or close the tab and never come back.

Why most error messages fail the second and third test

There's a useful diagnostic framework for error messages. Three questions in sequence:

  • Avoid: What should a user check before attempting this action?
  • Explain: What actually happened?
  • Resolve: What specific action should they take next?

Most error messages pass the first test and fail the second and third.

"Password must be at least 8 characters" tells users what to check. It explains the rule. But it doesn't explain what happened (their specific password was too short) and it certainly doesn't resolve the situation (should they change the password they just typed? Start over? Is there a strength indicator?).

"Payment declined" fails all three. It doesn't tell users what to check, it doesn't explain the specific reason for the decline, and it gives no path forward. Was it the card number? The billing address? A fraud flag? The user has no idea, and now they're deciding whether to try again, call their bank, or abandon checkout entirely.

The user flow should not end at an error state. The error state is part of the flow. Design it accordingly.

The writing is the design

A piece from UX Design puts it well: writing is not decoration applied to a design that already exists. The writing is the design. The language users encounter at friction points — during errors, confirmations, empty states — is the experience. Not the visual chrome around it.

This is especially clear with error messages because the visual design of an error state is almost always correct. The red colour, the warning icon, the positioning — these are fine. What fails is the content inside them.

The content problem usually comes from one of three places:

Technical language copied from the backend. Error codes, SQL exceptions, HTTP status messages. None of these mean anything to users and most of them actively confuse the situation. "Error 403: Forbidden" tells a user their action was blocked, not why, and not what to do about it.

Vague language that avoids responsibility. "Something went wrong" is the canonical example. It sounds polite and neutral. It contains zero information. Users cannot act on it, and they cannot tell if the error is on their side or yours.

Missing next steps. The explanation is present, but the resolution is absent. The user now knows what went wrong. They still don't know what to do next.

Applying Avoid, Explain, Resolve

The framework is most useful when applied concretely to specific error types.

Form validation:

Before (typical): "Email address is invalid."

After: "Check your email address — it looks like there might be a typo after the @ symbol. Enter it again below."

Avoid: implies what to check. Explain: identifies the specific problem. Resolve: tells them the action.

Payment errors:

Before: "Payment failed."

After: "Your card was declined. This is usually because the billing address doesn't match what your bank has on file. Double-check the address, or try a different card."

Avoid: tells them what to check. Explain: gives the most likely cause. Resolve: two clear options.

Upload errors:

Before: "File type not supported."

After: "That file type can't be uploaded here. Try saving it as a PDF, PNG, or JPEG first, then upload again."

Avoid: implicitly tells them the problem exists with the format. Explain: clarifies the constraint. Resolve: specific formats and action.

The pattern is always the same: say what to check, say what happened, say what to do next.

Accessibility and error messages

Error messages have an additional layer of responsibility beyond user flow: they must be accessible. This is frequently overlooked because error states are treated as afterthoughts, and accessibility audits often focus on the happy path.

The core requirements for accessible error messages:

  • They must be programmatically associated with the field they relate to (using aria-describedby or similar)
  • They must not rely solely on colour to communicate that an error has occurred
  • Screen readers should announce errors appropriately — ideally without forcing users to navigate away from the field they're correcting

Screen reader compatibility is particularly relevant here. An inline error message that appears visually next to a form field may not be read aloud at all if it isn't correctly associated with that field. The user completes the form, submits, and has no idea what went wrong.

The practical test: tab through your error states with a screen reader. Not to check a box — to find out what users who rely on assistive technology actually experience. Most teams that do this for the first time find significant gaps.

If you want more thinking like this, Unicorn Club is a free weekly newsletter for senior designers and product teams.

Common mistakes UX designers still make

A piece on UX Planet cataloguing common UX mistakes notes that error message design is consistently one of the areas where the gap between intention and execution is widest. The problem statement is almost always correct — teams know they want helpful error messages. The execution fails because error states are designed under time pressure, without the same rigour applied to the primary flows.

The specific mistakes that appear most often:

Confirm-shaming in error adjacent copy. "Are you sure you want to leave? Your changes will be lost." This is a friction tactic dressed as an error warning. It's manipulative and it erodes trust.

Errors that dismiss themselves. Toast messages that disappear after three seconds are invisible to users who are looking at something else. Error states that need to be acted on should not auto-dismiss.

One error shown at a time. Showing validation errors one at a time — each time the user submits — makes form completion feel like a guessing game. Show all errors on submission, not in sequence.

No error state for good states. Empty states, success states, and warning states are part of the same system as error messages. Design them together or the UX becomes inconsistent.

A checklist for your next error state review

Before shipping any error state, check it against these:

  • Does it tell the user what to check before they try again?
  • Does it explain specifically what happened — not generically, but in this particular case?
  • Does it tell the user exactly what action to take next?
  • Is it free of technical language (error codes, internal terminology)?
  • Is it accessible — programmatically associated, not colour-only, readable by screen readers?
  • Does it stay on screen long enough to be read and acted on?

If the answer to any of these is no, it's not ready to ship. That's the standard. Error states are primary interface. Treat them that way.