HTML Input Validation is (maybe) Good

by Wes Goulet published on

I think of client-side validation as a progressive enhancement for your users. You have to validate user input on the server (you can't trust what comes from the client), but some validation on the client makes for a nice UX. But that doesn't have to mean lots of JS code or using some validation library on your client. You can get pretty far with the browser's built-in HTML input validation. And then you can layer a little bit of JS on top of that to make it even better.

Let's take a look at a text input. You can use pattern, minlength and maxlength to provide constraints. You can use title to provide error message. You can conditionally style invalid input with :user-invalid.

Example

<label for="program_name">Name of program</label>
<input
type="text"
id="program_name"
minlength="3"
maxlength="20"
pattern="[a-zA-Z0-9]+"
title="Only alphabetical and numerical characters are accepted"
required
/>
input:user-invalid {
border: 4px solid red;
}

When the user attempts to submit the form the browser takes care of validating and showing the message.

A screenshot of Chrome.  The error message says 'Please match the requested format. Only alphabetical and numerical characters are accepted'
Example validation message on Chrome
A screenshot of Firefox.  The error message says 'Please match the requested format: Only alphabetical and numerical characters are accepted.'
Example validation message on Firefox
A screenshot of Safari.  The error message says 'Match the requested format: Only alphabetical and numerical characters are accepted'
Example validation message on Safari
A screenshot of Safari on iOS.  The error message says 'Match the requested format: Only alphabetical and numerical characters are accepted'
Example validation message on Safari on iOS

You can play with a live example at this CodePen.

BTW, I just noticed that Chrome and Firefox say "Please" but Safari doesn't 😃

Styling

You can't style the error popup, so if that's important to you then maybe you need to write your own error UI. A lot of times I don't mind leaning on browser UI when it's available (ie: most of the time I don't need my error messages in my website's overall brand/styling). Also, I think a lot of users have seen the browser's error UI before (from other sites that use native form validation), so there is some familiarity there for the user.

The Problem: Accessibility

Unfortunately, native form validation isn't very accessible.

I had assumed it was accessible, because most of the time when I lean on the browser to do something it handles accessibility much better than any userland code I would write. But after reading this excellent post from Adrian Roselli (an accessibility expert), I learned my assumption was wrong.

The main accessibility issues with native form validation are:

  • Error messages aren't associated with form fields - Screen readers don't reliably announce which field has an error, so users relying on assistive technology can't easily figure out what needs to be fixed.
  • Error messages disappear too quickly - The browser's error bubble might disappear before users can read it.
  • Error messages don't respect user text size/spacing preferences - The error bubble text doesn't resize with browser zoom settings or respect text spacing preferences.

Related to all this, a recent update to WCAG acknowledges these accessibility issues with native form validation.

Make it better with JS

I originally wrote this post to point out that native form validation is good enough (lean on the browser!) and that's all you need. But after reading about the accessibility issues, I think the right answer is using native form validation as the foundation, and then adding a bit of JS to make it more accessible.

We'll use the Constraint Validation API, which is the JavaScript interface for HTML form validation. It allows you to programmatically check if a form field is valid, get validation messages, and customize how errors are displayed to users. For example, you can check if an input is valid:

const element = document.getElementById("program_name");
console.log(element.checkValidity()); // returns true or false

You can also get the validation message:

const element = document.getElementById("program_name");
console.log(element.validationMessage); // returns the error message if invalid

We'll use the Constraint Validation API and follow WCAG input error guidance to create our own error messages that are properly associated with form fields. Here's a simple example:

<form id="my-form">
<label for="program_name">Name of program</label>
<input
type="text"
id="program_name"
minlength="3"
maxlength="20"
pattern="[a-zA-Z0-9]+"
title="Only alphabetical and numerical characters are accepted"
required
aria-describedby="program_name-error"
/>

<span id="program_name-error" role="alert"></span>
</form>
const form = document.getElementById("my-form");
const input = document.getElementById("program_name");
const errorMessage = document.getElementById("program_name-error");

// IMPORTANT: set this attribute in JS, that way it's a
// progressive enhancement (ie: if JS isn't available the
// native form validation will still work).
form.setAttribute("novalidate", "");

function validateInput() {
const isValid = input.checkValidity();
errorMessage.textContent = isValid ? "" : input.validationMessage;
if (isValid) {
input.removeAttribute("aria-invalid");
} else {
input.setAttribute("aria-invalid", "true");
}
}

// Validate on blur (when user leaves the field)
input.addEventListener("blur", validateInput);

// Clear errors as user types
input.addEventListener("input", () => {
if (input.checkValidity()) {
errorMessage.textContent = "";
input.removeAttribute("aria-invalid");
}
});

// Handle form submit
form.addEventListener("submit", (e) => {
if (!form.checkValidity()) {
e.preventDefault();
// Update validation state for all fields
validateInput();
}
});

In this example:

  • The form has novalidate to turn off the browser's built-in validation (added via JavaScript so it degrades gracefully).
  • The error message is associated with the input using aria-describedby, so screen readers will announce it when the field is focused.
  • The error message has role="alert" so screen readers will announce it when it appears.
  • The input gets aria-invalid set appropriately, clearly marking it for assistive technologies.
  • Validation happens on blur (when the user leaves the field) and on form submit.
  • The error message stays visible, giving users time to read it.

You can play with a live example at this CodePen.

This post by Cloud Four spells out a more complete solution in detail. (If you mainly support evergreen browsers then I wouldn't worry too much about the first part "Removing invalid styles on page load for all browsers" since Chrome has shipped support for :user-invalid for a couple years now.)

So native HTML form validation is a good starting point, but it's not enough on its own due to accessibility issues. You can use the Constraint Validation API to layer on accessible error messages with a bit of JavaScript. That way you get the browser's validation working as a baseline, and then enhance it to be accessible when JS is available.

Thanks to Manuel for reviewing this post and pointing out the accessibility issues with native form validation.

About Wes Goulet

Maker of (hopefully) useful things on the web. Microsoft/Salesforce alum. Big fan of PWAs, SSGs, web standards, and simple/boring code.

Site: goulet.dev

Comments

There are no comments yet.

Leave a comment

HTML is not supported, but you can use Markdown.