Form Validation Styling on Input Focus

Avatar of Geoff Graham
Geoff Graham on
/* Only show invalid ring while not focused */
input:not(:focus):not(:placeholder-shown):invalid {
  border-color: var(--color-invalid);
}
input:not(:focus):not(:placeholder-shown):invalid ~ .error-message {
  display: block;	
}

/* Only show valid ring while not focused and if a value is entered */
/* :empty won't work here as that targets elements that have no childeren. Therefore we abuse :placeholder-shown */
input:not(:focus):not(:placeholder-shown):valid {
  border-color: var(--color-valid);
}

Pulling this straight from the Weekly Platform News, where Šime Vidas covers two long ongoing issues with using :invalid to style form input validation. Apparently, what happens is…

  • [inputs] become :invalid while the user is still typing the value.
  • If a form field is required (), it will become :invalid immediately on page load.

Both of these behaviors are potentially confusing (and annoying), so websites cannot rely solely on the :invalid selector to indicate that a value entered by the user is not valid. However, there is the option to combine :invalid with :not(:focus) and even :not(:placeholder-shown) to ensure that the page’s “invalid” styles do not apply to the <input> until the user has finished entering the value and moved focus to another element.

Ryan Florence sums this up nicely in a tweet:

So, what this snippet does is enhance :invalid by combining it with :not(:focus) and :not(:placeholder-shown).

What does that mean? Let’s translate the code into something more readable.

input:not(:focus):not(:placeholder-shown):invalid {}

If an input is not in focus, its placeholder text isn’t shown, and the entered text is invalid… then you can use these styles.

In other words, this prevents the invalid style from being applied until text is entered and focus moves to another element.

input:not(:focus):not(:placeholder-shown):invalid ~ .error-message

Hey, let’s display the error message if those same conditions are met.

This is the exact same thing as above, but selects an .error-message class and sets it from display: none to display: block only after the text is entered and focus moves from the input to something else.

input:not(:focus):not(:placeholder-shown):valid

Oh, the text that’s entered is valid? OK, let’s apply these styles instead.

Again, the same sorta condition, but chained to :valid instead of :invalid. That way, we can give the user a different set of styles to indicate that what they typed is good to go!

According to Šime, this sort of snippet might be unnecessary in the future as a greater push is being made for :user-invalid and :user-valid. Firefox already intends to ship its un-prefixed solution. Tickets are open to do the same in Safari and Chrome.