Skip to main content

Command Palette

Search for a command to run...

A deep dive into :invalid, :user-invalid

Published
3 min read
A deep dive into :invalid, :user-invalid

When we talk about web accessibility (a11y), we usually think of ARIA attributes, screen readers, or keyboard navigation first.
But in real user experiences, the most frequently encountered accessibility challenge is forms.

“Is this email format correct?”
“This field is required.”
“Your password is too short.”

At the core of this experience are two CSS pseudo-classes:

  • :invalid

  • :user-invalid

In this article, we’ll break down what they mean, how they differ, and how to use them correctly.

✨ 1. :invalid — The browser’s validation state

:invalid represents the browser’s native constraint validation state for form controls. It is automatically applied based on HTML5 form validation rules.

For example:

<input type="email" required />

As soon as the page loads, this input is considered :invalid, because it violates the required constraint.

Important takeaway

:invalid does not mean “the user made a mistake.”
It simply means “the current value does not satisfy the validation rules.”

Because of this, styling directly with :invalid often causes poor UX:

  • Red borders before the user types anything

  • Error messages shown before any interaction

✨ 2. :user-invalid — Invalid after user interaction

To address this problem, CSS introduced :user-invalid (Selectors Level 4).

The key idea is simple:

:user-invalid only applies after the user has interacted with the form control.

You might now say, “Now it makes sense!”

❌ 3. Don’t mix :invalid and :user-invalid together

Let’s say you do this:

  • Form A uses :invalid

  • Form B uses :user-invalid

  • Different forms

  • Different wrappers

  • Different CSS scopes

You expect them to behave independently.

But they don’t.

As soon as any :invalid selector exists in the document,
:user-invalid starts behaving like plain :invalid.

Errors appear before user input.

This happens even if:

  • the forms are separate

  • the selectors are scoped

  • the DOM structure is clean

🤔 4. Why this happens (the real reason)

:user-invalid is not an independent state

It depends on :invalid.

Internally, browsers must compute the :invalid state first in order to determine whether :user-invalid applies.

It’s also important to understand that CSS selectors are evaluated at the document level, not at the form level. CSS has no notion of “scope” in this context — selectors are always matched against the entire document tree.

Once the browser encounters any selector that references :invalid, it switches the document into a constraint-validation-aware state. As part of this optimization, the browser eagerly computes validation states for form controls.

During this process, the “user interaction delay” condition that :user-invalid is meant to provide can be effectively bypassed. This behavior is not a specification violation, but rather an intentional implementation trade-off made by browser engines to balance correctness and performance.

Next time,

we’ll dive into the ARIA attributes used in forms.
Let me know in the comments if there are any attributes you’ve been curious about! 😉

a11y - HTML

Part 1 of 1

In this series, I share articles on web accessibility along with tips and insights about semantic HTML markup.