Writing the validation rule is the easy part. Knowing when to show the error is the hard part.
The Art of Telling Users They're Wrong
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
| Strategy | When it runs | Best used for | Pro | Con |
|---|---|---|---|---|
| Real-time (inline) | As the user types | Password strength meters | Highly interactive | Disruptive if triggered too early |
| On-blur | When focus leaves the input | Email formats, phone numbers | Good balance, non-intrusive | Misses errors if the user submits without leaving |
| On-submit | Clicking the submit button | Final checks, matching passwords | Cleanest flow while filling | Frustrating 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.
Rheadโs two most practical tips: donโt validate an empty field on blur (wait for actual input), and always back blur up with on-submit validation so nothing slips through.
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:
<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.
The baseline, whatever timing you pick
- Link each error to its input with
aria-describedby, and setaria-invalid="true"on the input - Put error containers in
aria-live="polite"so theyโre announced without hijacking the screen reader. Live regions arenโt the problem Ferry describes; firing them too early is. Nothing should be announced until the user has actually typed into the field - For longer forms, show an error summary after submit that links to each field
- Donโt rely on colour alone. Pair the red with an icon and actual words, since roughly 8% of men have some form of colour vision deficiency
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-liveso 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:
- Stay quiet while people type, unless youโre helping (formatting, strength meters) or the error is obvious from the first character.
- Check on blur, but only fields that someone has actually typed into.
- Always check on submit, for empty fields, cross-field rules and anything blur missed.
- Once a field has an error, clear it the moment itโs fixed.
- 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.
