Writing the validation rule is the easy part. Knowing when to show the error is the hard part.

A A A A A

The Art of Telling Users They're Wrong

2,599 words ยท 148 sentences ยท 98 paragraphs ยท Avg: 18 words/sentence (Hemingway: ~14) ยท Flesch: 67 Standard

Recently, my PM, our UI/UX designer and I (read: frontend engineer ft. part-time PM as well) spent a good chunk of a meeting debating a single input field.

Not its layout, not its label. Just when the form should tell users their business registration number (BRN) is wrong.

For context, the BRN isnโ€™t just another random field in our product. Itโ€™s one of the key identifiers for a company in PayNetโ€™s Single Sign-On (SSO)โ€”the layer that handles authentication and authorisationโ€”so a lot hinges upon it. If itโ€™s wrong, everything tied to it is wrong too.

On one side: if the BRN is that important, why not just check it as the user types? Catch the mistake the moment it happens, before it cascades down everywhere.

On the other: a BRN is a long string of characters. Telling someone itโ€™s invalid three characters in is justโ€ฆ overreacting. Let them finish, and check it only when they submit.

Although the meeting ended with the conclusion to just maintain status quo for now (validate upon submission), the core underlying question is still stuck with me. It made me realise Iโ€™d never thought that hard about it.

Writing the validation rules is the easy part. When to show the error is where it gets hard.

So as usual, I went down a rabbit hole of articles and research and this post is my attempt to put it into words.


The core question

Every validation strategy comes down to one question: has the user finished?

Thatโ€™s the whole problem. As Vitaly Friedman puts it in his Smashing Magazine piece on inline validation, we canโ€™t really know when someone is done with a field unless they tell us. Everything else is a guess based on what they did:

  • Real-time (inline) guesses on every keystroke
  • On-blur guesses when focus leaves the field
  • On-submit doesnโ€™t guess. It waits until the user explicitly says theyโ€™re done
StrategyWhen it runsBest used forProCon
Real-time (inline)As the user typesPassword strength metersHighly interactiveDisruptive if triggered too early
On-blurWhen focus leaves the inputEmail formats, phone numbersGood balance, non-intrusiveMisses errors if the user submits without leaving
On-submitClicking the submit buttonFinal checks, matching passwordsCleanest flow while fillingFrustrating when there are many errors at once

On paper, that table makes it look like you pick one row and move on. In practice, you mix them.


Real-time (inline)

This was one side of our BRN argument, and probably the one most of us reach for by default. It feels responsive, sophisticated and modern, and frameworks and libraries make it really easy these days. Wire the validator to onChange and youโ€™re done.

The problem is that a half-typed email is not a wrong email. rahiman@gm isnโ€™t an error, itโ€™s an email in progress. Flagging it is like someone reading over your shoulder and correcting you mid-sentence. Annoying, right?

Pawel Klasa makes the strongest case against it in Form validation is hostile. His line is blunt: keystroke is never the right time to tell someone their email is malformed. Keystrokes are for things that help while you type, not things that judge you:

  • Formatting help, e.g. adding spaces to a card number as you go
  • Progress indicators, like a password strength meter or a checklist of password rules lighting up
  • Async lookups, e.g. โ€œthis username is takenโ€

Friedman adds one reasonable exception: errors you can spot from the very first character. If a policy number can only contain digits and someone types a letter, thereโ€™s no point letting them type twelve more characters before you mention it. You could save them precious time by alerting them of their mistake early on instead of leading them on.

Apart from those, real-time correctness checks mostly add noise.


On-blur

On-blur is the closest thing to a crowd favourite. Itโ€™s the one that I use most frequently on the frontend as well. The user leaves the field, which roughly means theyโ€™ve finished with it, so thatโ€™s when you check.

Thereโ€™s research behind it too. Luke Wroblewskiโ€™s inline validation study back in 2009 compared validating after a field (i.e. on blur) with validating only on submit. The on-blur version had a 22% higher success rate, 22% fewer errors, 31% higher satisfaction and 42% faster completion times.

As Robert Rhead puts it, people get feedback while theyโ€™re still thinking about that part of the form, and the errors show up one at a time instead of all at once at the bottom of a long form.

But blur has its own shortcomings or gotchas, and the more I read the more of them I found:

  • It assumes people always fill forms top to bottom. Newsflash: They donโ€™t. They skip ahead, come back, and tab around to see whatโ€™s coming.
  • It fires on fields people merely passed through. Tab through an empty required field without typing and you get a โ€œThis field is requiredโ€ for a field you havenโ€™t even tried to fill.
  • It misses the last field. If you type into a field and hit Enter to submit without ever leaving it, blur never runs. Thatโ€™s why it has to be paired with a submit check.

On-submit

On-submit is the most patient option. Nothing happens until the user clicks the button.

Itโ€™s also the one that gets the worst reputation, and on its own it often deserves it. fomr.io describes the familiar result: you fill in a long form, hit submit, and get a pile of red that sends you scrolling back up to hunt for whatever went wrong.

Still, on-submit does a few jobs nothing else can:

  • Empty required fields. The only fair time to complain that you left something blank is when youโ€™ve said youโ€™re done.
  • Cross-field checks. โ€œPasswords donโ€™t matchโ€ only makes sense once both fields are filled.
  • Short forms. On a login form with two fields, validating anything earlier is probably overkill.

Friedman also suggests something Iโ€™m rather fond of: if a form is long enough that submit-time errors feel overwhelming, the form is probably too long. Consider splitting it into smaller pages, each with one task and its own submit, makes the wall of errors much less likely.

Thereโ€™s also a class of errors that donโ€™t belong to any single field at all i.e. you canโ€™t display the inline errors to 1 specific field, and on-submit is usually the only place they can show up. Basically, anything only the server can answer:

  • A BRN in a perfectly valid format that turns out to be already registered
  • A session that expired while the user was halfway through the form
  • A request that failed because of the network or a server hiccup

No amount of blur or keystroke validation catches these, because the browser simply doesnโ€™t know yet. Theyโ€™re best shown as one message at the top of the form (or right by the submit button), with focus moved to it so screen reader users hear it too.

The catch, as Klasa points out, is not to lean on this for things the browser could have checked. If a round trip to the server is the first time someone finds out their BRN is two digits short, thatโ€™s what he calls late validation, and that oneโ€™s on us.


Reward early, punish late

This is the so-called holy grail of form validation philosophy that I got from my readings.

The pattern, which Friedman credits to Mihael Konjeviฤ‡, treats a field differently depending on its state:

  • If the field is currently valid (or untouched), stay quiet while the user types. Only flag new problems once theyโ€™re done, i.e. on blur or submit. Punish late.
  • If the field is currently showing an error, re-check it on every keystroke so the error disappears the moment itโ€™s fixed. Reward early.

When I first read it, it sounded obvious. You donโ€™t nag someone while theyโ€™re typing, but once theyโ€™re fixing something youโ€™ve pointed out, you tell them straight away that itโ€™s fixed. Nobody wants to fix a typo, then have to click somewhere else just to make the red text go away.

72technologies frames it the same way and points out that some form libraries already support this. In Vue, VeeValidate gets you there by switching a fieldโ€™s listeners based on whether itโ€™s showing an error:

BrnField.vue
<script setup>
import { computed } from 'vue';
import { useField } from 'vee-validate';

const { value, errorMessage, handleChange } = useField('brn', validateBrn, {
  validateOnValueUpdate: false,
});

const listeners = computed(() => {
  // Valid or untouched: stay quiet while typing, check on blur (punish late)
  if (!errorMessage.value) {
    return {
      blur: handleChange,
      change: handleChange,
      input: (e) => handleChange(e, false),
    };
  }

  // Showing an error: re-check on every keystroke (reward early)
  return { blur: handleChange, change: handleChange, input: handleChange };
});
</script>

<template>
  <input :value="value" v-on="listeners" />
  <p v-if="errorMessage">{{ errorMessage }}</p>
</template>

A few lines, and the form stops punishing people for typing.

Klasa goes one step further. His main point is that validation timing should be decided per field, not per form. An email field, a password field and a โ€œconfirm passwordโ€ field each need something different. Choosing one mode for the whole form is how you end up with a form thatโ€™s too eager in some places and too late in others.


The accessibility angle

Honestly, I hadnโ€™t thought about this at all.

Not that it should come as a surprise. If Iโ€™m being honest, accessibility has always been the thing that gets relegated to the backseat. Nobody ever says it doesnโ€™t matter. It just rarely makes it into the PRD or the Figma file, so it ends up as a nice-to-have thatโ€™s revisited once everything else ships. Which, more often than not, means never.

In Why real-time form validation can become an accessibility issue, Helena Ferry points out that many screen reader users tab through the whole form first to understand what itโ€™s asking for before filling anything in.

Now picture that with eager blur validation. They tab past the name field: โ€œName is required.โ€ Past the email field: โ€œEmail is required.โ€ Past the phone field: โ€œPhone number is required.โ€ Their first pass through the form, the one meant to explain what itโ€™s asking for, turns into a stream of errors read out in place of the labels and instructions. If those errors sit in a live region, each one also interrupts whatever was being read at the time.

And itโ€™s not just screen readers. Errors that show up too early or too often are stressful for people with cognitive disabilities, and messages that appear and push the layout around can disorient people using screen magnifiers, because the thing they were looking at has just moved.

WCAG doesnโ€™t actually require or ban real-time validation. Criteria like 3.3.1 Error Identification, 3.3.3 Error Suggestion and 4.1.3 Status Messages cover how errors are communicated and leave the when up to us.

Ferryโ€™s recommendation: only validate after someone has meaningfully interacted with a field, meaning they typed something, picked something, or otherwise showed intent. Tabbing past doesnโ€™t count.


Consensus is a rare thing

For a topic that sounds this settled, the articles donโ€™t agree on as much as I expected.

On the default strategy:

  • fomr.io: on-blur is the best option, on-submit the worst
  • 72technologies: on-submit first, then re-check on every keystroke once a field has an error. On-blur only for fields with an obvious format
  • Friedman: on-blur in general, but submit-only for short forms

Read one article and on-blur is the answer. Read another and itโ€™s on-submit.

On how bad on-submit is:

  • fomr.io: the worst of the three
  • Klasa: โ€œlazy validationโ€ (silent until submit, then a wall of errors) is one of his six ways forms go wrong, yet submit is still the safety net every form needs
  • Friedman: good enough to design whole flows around

On โ€œneverโ€:

  • Klasa: keystroke is never the time to tell someone theyโ€™re wrong
  • Friedman: except for errors you can spot from the first character

On live regions:

  • Rhead and 72technologies: use aria-live so screen readers announce errors
  • Ferry: errors announced through live regions become a stream of interruptions

If youโ€™re astute enough and you read closely, these two arenโ€™t really in conflict. Ferryโ€™s issue is announcements firing before the user has done anything, not live regions themselves.

If anything, the disagreements make me trust the advice more. Nobody has a rule that holds for every form, but they all agree on two things:

  • Donโ€™t judge a field before the user has had a fair go at it
  • Make the error go away as soon as itโ€™s fixed

The rest is a judgement call per field. Which is, funnily enough, the answer our BRN meeting kept circling without landing on.


Go try it yourself

I could rant incessantly and dissect the topic even deeper, but Klasa ends his article with a line that suits this section better than anything Iโ€™d write: โ€œStop reading. Go feel it.โ€

He built a validation playground that runs the same five-field form under different timing strategies. In his own testing, the eager mode showed users 40 errors, while the per-field mode showed zero or one. Same form, same rules. The only thing that changed was the timing.

Try typing an email slowly in each mode. Youโ€™ll feel the difference before you can explain it.

Cramped on mobile? Open the playground in a new tab.


Conclusion

Looking back at that BRN discussion, I donโ€™t think real-time or on-submit was the right answer. It was a false choice. Both sides wanted the same thing, which was a correct BRN, and they were really arguing about when the user should hear about a mistake.

If we had that meeting again, Iโ€™d suggest splitting the job up. Stay quiet while the BRN is being typed. Check the format once they leave the field. Do the heavier checks when they submit. And if an error does show up, clear it the moment they fix it. The BRN gets the same scrutiny either way, but the user isnโ€™t scolded while theyโ€™re still typing.

If I had to boil it all down for the next form:

  1. Stay quiet while people type, unless youโ€™re helping (formatting, strength meters) or the error is obvious from the first character.
  2. Check on blur, but only fields that someone has actually typed into.
  3. Always check on submit, for empty fields, cross-field rules and anything blur missed.
  4. Once a field has an error, clear it the moment itโ€™s fixed.
  5. Decide per field, not per form.

One last point from Klasa that I keep thinking about: we spend a lot of time polishing the copy of error messages, making them friendlier and less blaming. Thatโ€™s worth doing, but a friendly error shown at the wrong moment is still a badly timed error. Get the timing right first, and then work on the wording.

When itโ€™s done right, validation barely registers. Nobody has ever complimented a form on its timing; they only notice when itโ€™s off.

As the Malay saying goes, biar lambat asalkan selamat. Better late than wrong. Just not so late that someone only finds out about a typo after theyโ€™ve hit submit.