In this third part of a three-part article we will continue our study of native form validation in browsers. Part 1 discussed general UI considerations and CSS. Part 2 studied a few HTML properties and the JavaScript API.
In this part we will consider the native error messages and offer general recommendations to come to actually usable native form validation.
((This article was originally published on Samsung Internet’s Medium channel. Since I do not believe Medium will survive in the long run I re-publish it here.)
As usual in my articles, I’m quite vague about exact browser compatibility patterns because I already collated that information in the inevitable compatibility table. You can find the gory details there.
So far we have been ignoring the details of the native error messages the browsers produce. The time has come to study them, and we will find these error messages also suffer from serious problems.
One point has to be made at the outset: these messages are not stylable (though they once were in Chrome). So there. By now we’re expecting this kind of thing.
When do browsers show native error messages? It turns out that there are only two triggers: if the user submits the form, and if reportValidity()
is called on an invalid form field. That’s not unreasonable. In both cases we want to warn the user about possible validation problems.
Of course, what neither trigger does is show a success message if a field has a valid value. Success messages are completely ignored in the API, where CSS at least has :valid
.
In part 1 we saw that onblur validation, i.e. showing an error message as soon as the user leaves the field, is the best timing. So the best way of triggering native error messages seems obvious:
field.onblur = function () { this.reportValidity() }
Seems simple, right? Unfortunately this misfires in a truly horrific way in Chrome and Safari, because in those browsers reportValidity()
, in addition to showing the error message, puts the focus on the offending field. Even worse, if a field receives the focus the error message is hidden. So what happens now is the following:
Try the effect here. First use Firefox or Edge for the proper result, then use Chrome or Safari for the buggy result.
The net result is that it appears to the user that she cannot leave the form field for unspecified reasons. This is truly horrible UX, and it leaves the user clueless as to what’s going on.
Firefox handles this better. It does not put the focus on the form field, and thus continues to show the error message and allows the user to understand what’s going on. Unfortunately it has one bizarre bug: it only shows the error message if the user clicks entirely outside any form field or puts the focus on the next form field. If the focus is moved to any form field but the next, the error message is not shown at all. Very weird.
As to Edge, it does not support reportValidity()
so this bit of code won’t work. Maybe Edge actually has the best implementation here.
When are error messages hidden? Most importantly, when the field is focused. This makes sense in some situations; sometimes the field gains the focus because the user starts editing the value. Sometimes it doesn’t make sense, though; the error message also disappears when the a field gains the focus programmatically, as we saw in the onblur example above.
All Chromium-based browsers, both desktop and mobile, hide the error message after five seconds have elapsed. I’m not sure this is a good idea: the user could miss the error message if she’s distracted during those five seconds, or needs some extra time to understand it.
Edge, Firefox, Safari, and most Chromia on mobile hide the error message when the user scrolls. This is not a very good idea: why wouldn’t the user be allowed to scroll?
If the user submits the form and thus triggers native error messages, it is quite possible that the form contains more than one error. What happens then? Nothing nice, as by now you’ll be able to guess for yourself. We cannot have nice things.
If the form is submitted the browser goes through all form fields in order, and if it finds an invalid field it stops and shows the proper error message. That may sound logical, but it has a few unexpected consequences in Chrome and Safari.
Try it here to see exactly what happens.
Chrome and Safari show the first error message and put the focus on the offending form field. The user corrects the value. Now what? The user thinks she’s ready and submits the form again, only to see a new error message pop up. This is bad UX. You shouldn’t be required to submit the form several times in order to find all error messages.
Firefox and Edge handle this much better: they give all invalid fields, and not just the first one, a red outline to denote that they are invalid. This is pretty much mandatory, and Chrome and Safari should implement it forthwith.
(And what about colour blindness? The red colour will not work, but the outline is still a little thicker than the usual form border. I am not aware of any research that proves or disproves that this works, so we have to trust the Mozilla and Microsoft browser teams here.)
In addition, Edge on desktop (but not on mobile) and Firefox on Android (but not 52 on Windows or Mac) pop op an error message every time the user focuses on an invalid field. This, now, is really helpful. The user sees a red outline and understands the field is imperfect. But why? Click on it and you’ll see the reason. Perfect! All browsers should implement this behaviour everywhere. Of course they won’t because Reasons.
Let’s talk about the invalid event for a bit. The invalid event? Yes, it exists. Even better, it turns out that all browsers support it. It fires, unsurprisingly, when a form field is found to be invalid. That is to say, when checkValidity()
or reportValidity()
find an invalid field, or when the submit process does so.
What can we do with this event? Not a bloody lot. The only really useful feature is suppressing the native error message by returning false
or using event.preventDefault()
. This is something you may want to do.
Next problem: the invalid event does not bubble up. Why not? Because browsers are weird.
Fortunately, there is an ancient trick that comes to our rescue. We have to set an invalid event handler on the form in the capturing phase, like this:
document.forms[0].addEventListener('invalid',function (e) { e.preventDefault(); },true);
This bit of code suppresses all native error messages, but the outline still appears in Firefox and Edge, and the form is not submitted. It is likely the most useful bit of code you’ll find in this entire article.
If there’s an invalid event it would make sense if there were also a valid event, right? It fires whenever a form field is validated and found to be valid. It would lead to simple scripts like this:
document.forms[0].addEventListener('invalid',function (e) { e.target.showErrorMessage(); },true); document.forms[0].addEventListener('valid',function (e) { e.target.hideErrorMessage(); },true);
It would be wonderful, and logical, if this event would actually work. Obviously, it doesn’t. Browsers are not impressed by our pathetic attempts at logic and stubbornly refuse to play along. We counter by not being impressed by browsers. That doesn’t help, but at least it makes us feel good.
That concludes our exhaustive and unsatisfying review of native CSS and JavaScript form validation in modern browsers. You want a conclusion? You’ll get one.
It doesn’t work.
Let me slightly qualify that. It’s perfectly fine for you to cherry-pick one or two good features and use them in your own custom script. However, this article set out to create a good form validation UX using only native features. That failed because the native features are lousy.
Every good idea in the Constraint Validation API or in CSS is offset by several very bad ones, and browsers make things even more messy by ignoring sensible UX precautions such as allowing the user to see form validation messages in all circumstances.
Despite having been implemented years ago, these features still not ready for prime time. Your users are much better served by an old-fashioned JavaScript form validation we’ve been writing for the past twenty years. (And please keep the script sensible; don’t fall for this sort of terrifying React shit.)
On the off chance that anyone from a browser vendor or standards body who actually cares about helping users and web developers reads this, here are my recommendations for native form validation that actually works:
:user-error
or :user-invalid
, so that form fields are only judged after the user has had the opportunity to do something. (Of all the recommendations I am making, this is the only one that’s likely to be implemented.):valid
and :invalid
should be triggered by a blur, and not a keypress. Users don’t want to be bothered while they’re filling out a form field. Show the results when they’re done.fieldset:invalid
and form:invalid
, label:invalid
should also work.maxlength
and minlength
should validate default values in the same way as all other constraints. (That may mean that all other constraints also wait for a user action; I don’t care. It’s consistency that I’m after.)input:before/after
. Supporting it would clearly be the best solution, but even consistent non-support would be preferable over the current nonsense in Chrome and Safari.error-message
to form fields, and an input:error-message
to style it. (Maybe add language-specific variants, such as error-message-en-us
, error-message-fr
, and so on, which take their cue from the defined document language?)title
contain the error message? In any case stop the current nonsense about title
only working on pattern
ed fields. Consistency!setCustomValidity()
into two methods: one to set the field’s error message text, and one to set the field’s validity to true or false.reportValidity()
.text
ones.Until these changes have been made, let’s quietly forget about native form validation. It’s more trouble than it’s worth.
This is the blog of Peter-Paul Koch, web developer, consultant, and trainer.
You can also follow
him on Twitter or Mastodon.
Atom
RSS
If you like this blog, why not donate a little bit of money to help me pay my bills?
Categories: