Progressively Enhanced Form Validation, Part 1: HTML and CSS

Progressively Enhanced Form Validation, Part 1: HTML and CSS

At 4/19/2024

Four icons. Icon 1: representing an invalid state, a fuscia circle shape with a white exclamation mark in the center. Icon 2: representing a valid state, a green circle shape with a white checkmark in the center. Icon 3: representing HTML, an orange badge shape with a white HTML element open/closing tag in the center. Icon 4: representing CSS, a blue badge shape with a light-blue paint brush in the center.

Browsers nowadays have built-in form validation (or “constraint validation”) features that are super helpful for highlighting when the entered data satisfies the necessary criteria. Some of us have relied on JavaScript-only solutions to handle client-side form validation in the past (/me slowly raises hand…), but as it turns out, most of these built-in features have been around for over a decade!

HTML5 introduced new mechanisms for forms: it added new semantic types for the <input> element and constraint validation to ease the work of checking the form content on the client side. Basic, usual constraints can be checked, without the need for JavaScript, by setting new attributes; more complex constraints can be tested using the Constraint Validation API.

MDN Constraint Validation docs

Join along as we explore progressively enhancing the form validation experience through a series of articles. Starting with the browser’s built-in validation features using HTML and CSS, then layering JavaScript for more enhancements, each successive article will build on the previous.

Feel free to view this article’s accompanying demo as a reference. The source code is available on GitHub.

I’m excited and hope you are too. Let’s dive into the foundation of any website: HTML and CSS.

Native form validation features can validate most user data without relying on JavaScript. These features provide the strong foundation required for building a progressively enhanced experience.

Let’s take a closer look at each feature.

It starts with choosing the most semantically appropriate value for the input type attribute (e.g., type="email").

<label for="customer-email">Email</label>
<input
  id="customer-email"
  name="customerEmail"
  type="email"
/>
Code language: HTML, XML (xml)

Other validation attributes can be added to layer more constraints (e.g., required, min/max, minlength/maxlength, pattern, etc.).

<label for="customer-email">
  Email <span aria-hidden="true">(required)</span>
</label>
<input
  id="customer-email"
  name="customerEmail"
  type="email"
  required
  minlength="2"
/>
Code language: HTML, XML (xml)

Value-checking pseudo-classes are available to help style form controls. Depending on your site’s design, you may not need all of them, but it’s good to know they exist.

To highlight valid or invalid fields.

To highlight required fields (specified by a required attribute) and optional fields (fields with no required attribute).

To highlight fields within or outside of the range limits specified by the field’s min/max attributes.

To highlight valid or invalid fields after the user has interacted with the field.

When a form is submitted, the browser automatically shows error messages in a “bubble” next to the first form control where the input data does not satisfy the form control’s validation constraints. The message and design of the bubble will differ from browser to browser. The styles of the error bubble cannot be modified, providing a consistent browser-specific user experience. However, this can also be a negative as the bubble design may not fit the site’s aesthetics.

The Chrome browser built-in error message bubble floating over an empty email field with the message, "Please fill out this field."
Each browser has its own error message bubble design. Shown above is Chrome’s design.

One of the pushbacks against using browser built-in form validation is that invalid styles are applied on page load before the user interacts with the form controls. I agree. This creates a confusing user experience.

A series of required empty input fields showing the "invalid" UI state on page load.
Seeing invalid form inputs on page load is a confusing user experience.

No worries, though, :user-invalid/:user-valid to the rescue!

These CSS pseudo-classes can progressively enhance the user experience in browsers that support them. In contrast to :invalid/:valid, using the :user-invalid/:user-valid pseudo-classes will apply the styles only after the user has interacted with the form control.

No more invalid UI styles on page load!

To support all modern browsers and apply :user-invalid/:user-valid in a progressively enhanced manner, the CSS could look something like this:

/**
 * For browsers that support :user-invalid/:user-valid
 */
input:user-invalid {
  /* Invalid input UI styles */
}
input:user-valid {
  /* Valid input UI styles */
}

/**
 * When not supported, fallback to :invalid/:valid
 * Wrapping :valid/:invalid in a "not" @supports block ensures 
 * that the invalid styles are not applied on page load in browsers 
 * that do support :user-invalid/:user-valid
 */
@supports not selector(:user-invalid) {
  input:invalid {
    /* Invalid input UI styles */
  }
  input:valid {
    /* Valid input UI styles */
  }
}Code language: CSS (css)

We can see the differences when viewing the Part 1 demo:

  • In browsers that support :user-invalid (e.g., new versions of Firefox and Safari), we’ll notice invalid styles do not get applied until after we interact with and move away from (blur) the form control.
  • In browsers that do not support :user-invalid (e.g., Chrome, though they are looking to ship support soon!), we’ll notice the invalid styles are applied when the page loads before we interact with the form controls.

The @supports CSS at-rule is supported in all modern browsers. But if the project needs to support IE, we’ll want to style the :invalid/:valid CSS rules outside the @supports block and then unset those styles inside the @supports block.

As with all things, there are always pros and cons. Let’s take a quick step back to review a few pros and cons of using browser built-in validation as the foundational base user experience.

  • No JavaScript is required!
  • No need to write custom validation logic
  • Can handle the majority of validation use cases
  • The validation errors are automatically rendered and styled by the browser providing a consistent browser-specific experience
  • Validation error messages are localized automatically

Thank you to Juliette Alexandria and Adrian Roselli for providing feedback and resources regarding WCAG violations for native browser validation features. Your feedback is greatly appreciated. 🙌🏽

You bet it can! We can address all of these cons by layering a bit of JavaScript combined with some ARIA attributes. We can build a progressively enhanced, more accessible experience on top of the native HTML & CSS validation features.

This is a fantastic example of how progressive enhancement shines. We provide a baseline user experience without JavaScript. The enhanced version builds on an already established foundation. We no longer need to rely on JavaScript-only solutions.

In the following article of this series, we’ll explore all of this, building out from where the Part 1 demo left off. See you there!

I’ve got you! Listed below are all of the articles from the series:

Copyrights

We respect the property rights of others and are always careful not to infringe on their rights, so authors and publishing houses have the right to demand that an article or book download link be removed from the site. If you find an article or book of yours and do not agree to the posting of a download link, or you have a suggestion or complaint, write to us through the Contact Us, or by email at: support@freewsad.com.

More About us