Skip to content
← Blog UI/UX October 15, 2026

Before & After: The Error State Redesign That Stopped Users From Rage-Quitting My Form

9 min read
Before & After: The Error State Redesign That Stopped Users From Rage-Quitting My Form

There's a particular kind of failure that's easy to miss because it doesn't generate an error log, doesn't crash anything, and doesn't trigger any alert. The user just leaves. Silently. And they don't come back.

That's exactly what was happening on a client project I inherited about eight months ago — a small-business web app for managing service bookings. The form was technically functional. PHP processed submissions correctly. MySQL stored the data cleanly. But Hotjar session recordings told a brutal story: users were hitting the submit button, seeing a wall of red error messages appear at the top of the page, scrolling down, apparently giving up, and closing the tab.

The abandonment rate at the validation step was 68%. Not a rounding error. Not noise. A systematic failure happening to more than two-thirds of users who reached that point.

This is a breakdown of what the form looked like before, why it was failing, and the specific changes I made to fix it — with the reasoning behind each decision.

What "Before" Actually Looked Like

The original form had seven fields: name, phone, email, service type (a <select>), preferred date, preferred time, and a notes textarea. Perfectly reasonable for a booking form. The validation logic itself wasn't wrong — it was checking required fields, validating email format, confirming the date wasn't in the past.

The problem was everything around the validation. Here's what the error experience looked like:

  • User fills out the form and hits "Book Now"
  • PHP processes the POST, finds three validation errors
  • Page reloads (full page reload — no JS validation at all)
  • A <div class="alert alert-danger"> appears at the very top of the page listing all three errors as a bulleted list
  • Every field in the form is now empty — the values weren't being repopulated from $_POST
  • No visual indication on the specific fields that failed

Read that third-to-last point again. The values weren't being repopulated. The user typed their name, phone number, email, selected a service type — and after hitting submit and getting errors, all of it was gone. They were staring at a blank form again with a red paragraph at the top explaining what went wrong.

That's not a UX problem. That's a trust violation. You just destroyed the user's work.

Diagnosing What Was Actually Breaking Trust

I broke the issues down into three layers before touching anything.

Layer 1: The feedback was spatially disconnected from the cause

When users read "Please enter a valid email address" at the top of a page, they have to mentally map that error to the correct field. On a seven-field form this is annoying. On a longer form it's genuinely confusing. Eye-tracking studies and session recordings consistently show users scanning errors at the top, then scanning the form trying to figure out which field is the problem, then — frequently — giving up.

Error messages belong next to the fields they describe. Full stop.

Layer 2: No state preservation meant starting over

This was the most damaging issue and also the easiest to fix on the PHP side. The fix took about fifteen minutes:

<input
  type="text"
  name="customer_name"
  value="<?= htmlspecialchars($_POST['customer_name'] ?? '') ?>"
  class="<?= isset($errors['customer_name']) ? 'input-error' : '' ?>"
>

Every field now repopulates from $_POST on reload, with htmlspecialchars() to prevent XSS. The ?? '' null coalescing handles the initial page load where $_POST is empty. I also added the conditional class so fields with errors got a visible red border — not just a message somewhere else.

Layer 3: Submit-only validation with a full page reload

For a service booking form, a full page reload on every validation attempt is a particularly harsh experience. The user had to sit through a page load just to find out they forgot to fill in a field. Even on a fast connection that's a perceptible delay. On a slow mobile connection — which, in Indonesia, is a very real scenario — it was genuinely punishing.

The After: What I Actually Changed

Inline field-level errors, positioned directly below each input

I moved error messages from the top-of-page summary into small <span> elements immediately below each field. The PHP template now outputs something like:

<div class="field-group">
  <label for="customer_email">Email Address</label>
  <input
    type="email"
    id="customer_email"
    name="customer_email"
    value="<?= htmlspecialchars($_POST['customer_email'] ?? '') ?>"
    class="field-input <?= isset($errors['customer_email']) ? 'field-input--error' : '' ?>"
    aria-describedby="customer_email_error"
  >
  <span id="customer_email_error" class="field-error" role="alert">
    <?= $errors['customer_email'] ?? '' ?>
  </span>
</div>

The aria-describedby connection between the input and the error span matters for screen reader users — it's the kind of detail that's easy to skip but makes a real accessibility difference. I wrote about focus management in more depth in this post-mortem on sidebar navigation, but the same principles apply here: if focus lands on a field with an error, the screen reader should immediately understand why.

The error span is always rendered in the DOM (avoiding layout shift), but empty when there's no error. CSS handles the visibility based on whether it has content. This approach also means the field height stays consistent whether or not there's an error message — no jarring layout jump.

Client-side validation layered on top of server-side

I added vanilla JS blur-event validation for basic checks — required fields, email format — so users get feedback before they ever hit submit. Critically, I only show errors after the user has left a field, not while they're typing. Showing "Invalid email" when someone has typed "dan" is hostile. Wait until they tab away.

document.querySelectorAll('.field-input').forEach(function(input) {
  input.addEventListener('blur', function() {
    validateField(this);
  });

  input.addEventListener('input', function() {
    // Only re-validate on input if the field already has an error state
    if (this.classList.contains('field-input--error')) {
      validateField(this);
    }
  });
});

The second listener — re-validating on input only when an error is already showing — is the pattern I've landed on after trying several approaches. It means: once you've shown a user they made a mistake, update that feedback in real time as they fix it. But don't interrupt them with errors before they've finished.

Server-side validation still runs on submission. Always. JS validation is for experience, not for security.

Smarter error copy

The original error messages were technically correct and utterly useless. "Email is invalid." "Date is required." These tell the user what went wrong but not what to do. I rewrote every message to be instructional:

  • Before: "Email is invalid" ? After: "Enter a valid email address, like [email protected]"
  • Before: "Date is required" ? After: "Choose a date for your appointment — it can't be today or in the past"
  • Before: "Phone is invalid" ? After: "Enter a phone number with at least 9 digits, numbers only"

This is the microcopy layer that designers often skip because it feels minor. It isn't minor. When a user is frustrated and confused, a clear instruction is the difference between them fixing the problem and them leaving.

A soft summary for screen readers and keyboard users

I kept a condensed error summary at the top of the form — but I made it visually subtle (not a red alert box) and primarily for accessibility. It's an aria-live="polite" region that announces the number of errors after submission. "3 errors found. Please review the highlighted fields below." It doesn't list all the errors again — that's redundant now that they're inline — it just tells users something went wrong and orients them.

What the Numbers Looked Like After

Over the four weeks following the redesign, form abandonment at the validation step dropped from 68% to 31%. That's not a perfect number — there's always going to be some abandonment — but it represents a meaningful change in real users completing real bookings.

The session recordings changed character entirely. Users were now hitting submit, seeing an inline error highlight on the specific field, fixing it, and resubmitting. The interaction felt like a conversation instead of a punishment.

There was also an unexpected secondary benefit: support messages about "the form not working" dropped significantly. The previous experience was so confusing that users genuinely believed the form was broken. Improving the error experience improved users' perception of whether the product was reliable.

The Patterns That Transferred to Other Projects

I've reused variations of this approach on every form I've built since. A few things that have held up consistently:

  • Never clear field values on a validation reload. This should be a reflex. Every input repopulates from $_POST. Every time. No exceptions.
  • Inline errors, not top-of-page summaries. The summary is for accessibility context, not the primary error communication.
  • Blur-first, input-second for JS validation. This pattern avoids the hostile real-time-error experience while still providing feedback that feels responsive.
  • Instructional error copy, not diagnostic error copy. Tell users what to do, not just what went wrong.
  • Use aria-describedby and role="alert". It's not extra work — it's part of building the component correctly the first time.

If you're working on multi-step forms specifically, the state preservation patterns get more complex — I covered that in detail in my guide to multi-step forms with saved state, which goes into how I handle in-progress form data across steps without losing it.


Error states are easy to treat as an afterthought — something you bolt on after the "real" form design is done. But in my experience, the error experience is part of the core form design. For a significant portion of your users, it's the most important interaction in the entire flow. Design it like it matters, because it does.

Frequently Asked Questions

Should I validate form fields on blur, on input, or only on submit?

The best approach is a hybrid: validate on blur after first interaction, then re-validate on input once the user has already seen an error. Validating on every keystroke before the user has finished typing creates a hostile experience that flags errors before they're even real errors.

Is it better to show all errors at once or one at a time?

For short forms (under 6 fields), show all errors at once after submit — it respects the user's time. For long or complex forms, inline field-level errors shown immediately after blur tend to work better because they catch problems early before the user reaches the submit button.

What's the most common mistake developers make with inline validation?

Showing a "success" green checkmark on fields that don't need validation — like an optional notes textarea. This creates false signals and teaches users to ignore the validation indicators entirely, which is the opposite of what you want.

Share Twitter / X LinkedIn

Enjoyed this? Let's build something.

Start a project
Keep reading

More articles

The Navigation Sidebar That Broke on Mobile: A UX Post-Mortem on Focus Management and State Assumptions
UI/UX October 12, 2026
The Navigation Sidebar That Broke on Mobile: A UX Post-Mortem on Focus Management and State Assumptions
A sidebar nav that worked perfectly on desktop turned into a trap on mobile. Here's exactly what broke, why, and how I fixed it.
Read →
How I Build Multi-Step Forms With Saved State That Actually Feel Reliable
UI/UX October 8, 2026
How I Build Multi-Step Forms With Saved State That Actually Feel Reliable
Multi-step forms lose users at every transition. Here's how I build them with real saved state, smart validation, and UX that earns trust.
Read →
13 Dashboard Information Density Patterns I've Used to Stop Users From Feeling Overwhelmed
UI/UX October 1, 2026
13 Dashboard Information Density Patterns I've Used to Stop Users From Feeling Overwhelmed
Too much data on a dashboard doesn't make it powerful — it makes it paralysing. Here are 13 patterns I actually use to fix that.
Read →