The :user-invalid selector

Last week, I went down one of my usual rabbit holes while searching for something online, and that led me to learning about a fairly recent addition to CSS: the :user-invalid selector.

This selector fixes a long-standing issue with the much older :invalid selector. To demonstrate, let's say you want to style a required form input differently when a user tries to submit the form without filling the input. A naive solution would be to apply the style with the :invalid selector:

<form method="post">
	<label for="email">Email</label>
	<input type="email" name="email" id="email" required />

	<button type="submit">Subscribe</button>
</form>
input:invalid {
	outline: 2px solid red;
}

When you do this, you'll find that the invalid style gets applied to the input on page load, before a user even interacts with the form. The reason for this is that the required input is technically invalid on page load, as it lacks a value at that point.

This behaviour isn't a very good user experience though. It makes more sense to mark the input as invalid only after the user has interacted with it. In the past, one had to use a bit of JavaScript to achieve this with :invalid.

But not anymore. We can now use :user-invalid to target an input that's invalid after user interaction. The CSS spec mandates that the selector matches an invalid input after a user tries to submit the input's form. Browsers may however define additional user interactions that trigger the :user-invalid state. For example, Chrome & friends trigger the state when you type an invalid value into an input then move focus away from the input.

You can test out the selector and compare it to :invalid with the following CodePen:

As a final note, I'll add that there's a :user-valid companion to :user-invalid, and that both have been available in all major browsers since late 2023.