I recently embarked on improving the client-side form validation for a client.
There were about 400 lines of form validation code stuffed inside a 1000 line
form_helper.js. I looked for lightweight form validation scripts but after
some hemming and hawing I decided to try my hand (again) at native HTML5 Form

If you’ve ever experimented with HTML5 Form Validation, you’ve probably been
disappointed. The out-of-box experience isn’t quite what you want. Adding the
required attribute to inputs works wonderfully. However the styling portion
with input:invalid sorta sucks because empty inputs are trigger the :invalid
state, even before the user has interacted with the page.

I finally sat down and spent a couple days trying to make HTML5 Form Validation
work the way I want it. I had the following goals:

  1. Leverage browser-level feedback, free focus management and accessible
  2. Only validate inputs on submit
  3. Styling with .error class

With this wishlist in hand, I set off and found a solution that works with only
6 lines of code.

The Setup

First we create a basic HTML5 form with a required attributes on inputs we
want to validate…

<form action="/users/signup" method="post"> <label for="email">Email</label> <input id="email" type="email" required> ... <input type="submit" value="Submit">

And then we need the CSS for the error state…

input.error { border-color: red;

Again, we want to use a CSS class and not the input:invalid pseudo-class
because even before the user interacts with the form on the page,
input:invalid makes it look like the user made an error. This is a bad UX.
Never blame the user.

😱 Form Validation in 6 Lines of JS!

I instinctually reached for the submit event to try and manipulate error
handling. However, this won’t work because the invalid event from the inputs
block the form’s submit event from firing. It’s good that our form won’t try
to submit invalid data I wasn’t sure how to validate a form that was never
submitted. I tried a few grossly complex workarounds.

Eventually, I discovered the simplest solution by hooking into the input’s
invalid event. Just before the submit event, the browser performs a
form.checkValidity() check which checks all the inputs. All inputs with
invalid data fire the invalid event to say “Hey, I have invalid data!” It’s
here that we can apply the error class we need.

const inputs = document.querySelectorAll("input, select, textarea"); inputs.forEach(input => { input.addEventListener( "invalid", event => { input.classList.add("error"); }, false );

🎉 We did it! Now when a form is submitted, the invalid event fires on invalid
inputs, and we add an .error class which adds a red border around the input.
And we’re done! 172b. Form validation that fits in a tweet.

♿ The best part is we’re leveraging the browser’s focus management and
accessibility behaviors. When a form submission is prevented, the first
:invalid input gets focused and screen readers read out the error message for
that input. This is great. Focus management isn’t fun to code.

💪 We’re also leveraging HTML5 input types for common pattern matching.
input[type="email"] expects an email address and the error messaging will say
something along those lines. Same with input[type="url"],
input[type="number"], etc. HTML input types are basic but powerful stuff.

But wait there’s more complex things we can do!

If you like your forms to immediately flag errors after leaving each input
(which that’s fine, I’m not judging), it’s pretty easy to add that functionality. In fact, it’s just an additional 3 lines of code…

input.addEventListener("blur", function() { input.checkValidity();

This On :invalid inputs, it will trigger the invalid event and add the
.error class we programmed above.

I thought this was going to be awful and ruin my simple solution. Custom error
messaging could be gnarly as well. But NOPE. HTML gods spared me. Behold, regex
pattern matching in ZERO lines of JavaScript!

By using a title attribute to describe the expected data input and a pattern
attribute with a regex pattern, we can pretty much validate against anything we

<label for="alpha">Alphanumeric Only</label>
<input id="alpha" type="text" pattern="[a-zA-Z0-9]+" title="Only alphanumeric characters allowed" required/>

There’s even a website, HTMLPattern.com, that has
the regex for almost every kind of validation you’d ever want to create.

Demo: Putting it all together

Here’s a Codepen of a complete working form with Pattern Matching using this

See the Pen Native form validation onsubmit with pattern?? by Dave Rupert (@davatron5000) on CodePen.


Every solution is a system of tradeoffs. The major tradeoff we’re making here is
that in leiu of controlling the styling of client-side validation errors, we’re
leveraging the browsers native tooltip-style validation messages. It doesn’t
look the coolest, but it works really well for casual client-side validation.

Instead of being perscriptive about error messaging, we use what the browser
natively gives us. We’re of course doing server-side validation and are free to
style those errors however we want (usually that’s a more critical error and
needs a better UX).

If this is unacceptable, Hyperform.js is a pretty
small (32kb minfied / 9kb gzipped) reimplementation of the same HTML5 Form
Validation API in JavaScript, but with additions to iron out some weird parts.

Let me know if you try this and have any problems with the approach. I’ll
probably use this as a starting point for every project ever. Happy coding!