:valid and :invalid pseudo-classes

It is 2020 all modern browser support native form validation, and they also support styling valid and invalid form elements via CSS. In this article you will learn how to use the :valid and :invalid pseudo classes, when to use them and why it depends.

What are the :valid and :invalid pseudo classes #

By default, every form element is valid and therefore also the :valid pseudo-class will be executed. We can change that however by defining attributes. The following attributes are available today (2020) to get build-in validation: required, minlength, maxlength, min, max, type and pattern.

Here are two examples where :invalid would be applied:

<label for="text">Text</label>
<input type="text" required id="text" name="text">

<label for="email">Email</label>
<input type="email" value="a" id="email" name="email">

The first one because it is required and is empty, the second one because it has type="email" and the current value »a« is not an email address.

So, this way you can style form elements differently, based on if they are valid or not.

One thing to note here about minlength - in this case validation only happens after user input, so if you have the following in your HTML,

<label for="whatever">Whatever</label>
<input type="text" minlength="12" value="123" id="whatever" name="whatever">

the :valid selector will be applied. However, once the user enters the same value »123« or any other value with a minimum length below twelve the :invalid selector will be applied.

Blame the user #

The big issue with setting :invalid on page load is that we blame the user by saying »Look, there is an error - correct that«, although the user had no chance to not make the »error« as they haven't even done anything.

The same is true for :valid, we tell the user »Look, this is already valid« and the user may rightly ask »Why is this form element even there if it doesn't require any input from my side«

Let's have a look at some ways to change the default behaviour and try to not confuse users.

Restrict with CSS #

To not show the valid/invalid states without user interaction we can use CSS. In general there are two ways of doing this.

:focus #

The first approach is to only trigger :valid/:invalid when a form element has focus.

input:focus:invalid {
border: 2px solid red;

This way the user first have to interact with the page to get a different styling. This comes with other issues though, as once the user moves to another form element the previous one will lose the validation styling.

:placeholder-shown #

The other approach is to use the :placeholder-shown pseudo class.

input:not(:placeholder-shown):invalid {
border: 2px solid red;

This way, elements only get marked as invalid when the placeholder is not shown. We can enhance this further by only using :invalid when the form element is not focused.

input:not(:placeholder-shown):not(:focus):invalid {
border: 2px solid red;

When you hear placeholder, you may think »placeholders are bad for accessibility and shouldn't be used« and this is true to some point, as you should never replace a real label element with a placeholder text and should avoid placeholder text in most cases, but we don't need any text for placeholder to get this working, all we need is

placeholder=" "

That said, in some cases this may still be confusing for users, but it is probably the best approach we have.

Restrict with JavaScript #

Okay, so we can now switch to JavaScript to correct that to our needs and make it perfect for everybody. I long thought about the best way to do this and I always ended up with the conclusion that it is not worth it as every solution I can think of would still not be perfect and might do more harm than good in the end. That said, there might be a way to do this, and if you found such a solution please let me know.

:user-invalid #

To help with all the issues described here, there is a new proposed pseudo-class called :user-invalid. It is still in early stage, but it looks promising to solve the issues many have with :invalid. For example, a user agent (browser) may choose to have :user-invalid match an :invalid element once the user has typed some text into it and changed the focus to another element, and to stop matching only after the user has successfully corrected the input.

It is currently not supported in any browser, but Firefox has support for the non-standard pseudo-class :-moz-ui-invalid.

Not use colours alone #

One thing you should always keep in mind is, that you should not indicate valid/invalid states with colour alone. Red–green colour blindness affects a lot of people for example, and for them there is no/little difference between a green and a red border.

As form elements are replaced elements, we can't use pseudo elements (::before/::after) directly on them as of now, so as a workaround we can use an empty span next to form elements to show an icon for example.

<input type="text" required>
input:valid + span::after {
content: "";
background: transparent url(valid.svg) no-repeat 0 0;

input:invalid + span::after {
content: "";
background: transparent url(invalid.svg) no-repeat 0 0;

Browser support #

Support for :valid and :invalid is very good, and even works all the way back to Internet Explorer 10. However, if you want to use :valid or :invalid on the form element, note that it is not supported in Internet Explorer.

In general, you can safely use these pseudo-classes to enhance your forms without needing to add a polyfill for unsupported browsers.

Conclusion #

It depends, and it is complicated. As outlined in this article you should always carefully test your chosen approach with real users. It might be even better to not use :valid/:invalid as it might to more harm than good.

Resources #

MDN :valid

MDN :invalid

MDN: :placeholder-shown

MDN: Client-side form validation

Browser support for CSS selector :valid

Browser support for CSS selector :invalid

How to target non-empty but invalid input elements with CSS

Updates #