W3docs

CSS :invalid Pseudo Class

Learn how CSS :invalid styles form fields that fail validation constraints, with examples, gotchas, accessibility tips, and browser support.

The CSS :invalid pseudo-class matches form-associated controls — such as <input>, <select>, and <textarea> — whose current value fails the browser's built-in validation constraints. It is the visual half of HTML's constraint-validation API: the browser evaluates validity, and :invalid lets you style the result with pure CSS, no JavaScript required.

This page covers exactly when :invalid matches, how to style it without alarming users before they've typed anything, the radio-button quirk, the modern :user-invalid alternative, and how to keep error feedback accessible.

When does an element match :invalid?

A control matches :invalid whenever it carries at least one validation constraint and its current value violates that constraint. Common triggers:

ConstraintAttribute / typeFails when…
Required but emptyrequiredthe field has no value
Wrong email formattype="email"value isn't a syntactically valid e-mail
Wrong URL formattype="url"value isn't a valid absolute URL
Out of rangemin / max on type="number", type="date", etc.value is outside the allowed range
Wrong stepstepvalue doesn't align with the step interval
Pattern mismatchpatternvalue doesn't match the regex
Too long / too shortminlength / maxlengthvalue length is outside the allowed range

If a control has no constraints at all (a plain <input type="text"> with no extra attributes), it is considered unconstrained and neither :valid nor :invalid applies to it.

A <fieldset> element matches :invalid when any of its descendant form controls are invalid.

For the opposite side, see the :valid pseudo-class, which matches controls that pass all constraints. The :required pseudo-class matches required fields regardless of whether they have a value.

Syntax

:invalid {
  /* declarations applied to all invalid form controls */
}

Scope :invalid to a specific element type to keep styles predictable:

input:invalid,
textarea:invalid {
  border: 2px solid #c00;
  outline: none;
}

Basic example

The email field below is pre-filled with a malformed address ("not-an-email"), so it matches input:invalid on load and gets a red border.

<!DOCTYPE html>
<html>
  <head>
    <title>:invalid example</title>
    <style>
      input:invalid {
        border: 2px solid #c00;
        background-color: #fff0f0;
      }
      input:valid {
        border: 2px solid #090;
        background-color: #f0fff0;
      }
    </style>
  </head>
  <body>
    <h2>:invalid selector example</h2>
    <form>
      <label for="email">Email:</label>
      <input id="email" type="email" value="not-an-email" required />
    </form>
  </body>
</html>

Avoiding "premature red"

The most common :invalid pitfall: an empty required field is already invalid the moment the page loads, so a fresh form can light up red before the user has typed anything. That feels accusatory.

Option 1 — only style while the field is focused

Show the error border only while the user is actively in the field:

input:invalid:focus {
  border-color: #c00;
  outline: 2px solid #c00;
  outline-offset: 1px;
}

This is simple but disappears the moment the user tabs away, so a blank required field looks fine again.

Option 2 — hide the error while the placeholder is visible

:placeholder-shown is true when the placeholder text is displayed (i.e., the field is empty). Combining it with :not makes :invalid styling kick in only once the user has typed something:

/* Only show the error style when the field has a value that is invalid */
input:invalid:not(:placeholder-shown) {
  border-color: #c00;
}

This is effective but requires every field to have a placeholder attribute set — otherwise :placeholder-shown is never true and the guard does nothing.

Option 3 — use :user-invalid (modern standard)

The :user-invalid pseudo-class was designed specifically to solve this problem. It behaves like :invalid but only matches after the user has interacted with the control (typed in it, blurred it, or submitted the form):

/* Supported in all modern browsers as of 2024 */
input:user-invalid {
  border-color: #c00;
}

/* Fallback for older browsers */
@supports not selector(:user-invalid) {
  input:invalid:not(:placeholder-shown) {
    border-color: #c00;
  }
}

:user-invalid is the cleanest solution when you can rely on it. Firefox has supported it as :-moz-ui-invalid for years; the standard :user-invalid is now in all modern browsers.

Styling approach

A harsh full border at 2 px red is readable but jarring. Consider combining a border change with a subtle box-shadow for a softer look:

input:invalid:not(:placeholder-shown) {
  border-color: #c00;
  box-shadow: 0 0 0 3px rgba(204, 0, 0, 0.15);
}

Avoid relying on color alone — see the Accessibility section below.

Gotchas

Radio buttons

When a radio button group has required on one of its inputs, every button in the group matches :invalid while none is selected. Styling tiny radio circles isn't practical; instead, style the surrounding <fieldset> or <label>:

/* Style the fieldset, not the radio buttons themselves */
fieldset:invalid {
  border: 2px solid #c00;
  border-radius: 4px;
  padding: 8px 12px;
}

All radio buttons in a group share the same name attribute — that's what makes them a group in the browser's validity model.

Empty optional fields

A plain <input type="text"> with no required, no pattern, and no length constraints is always :valid even when empty. :invalid only fires when a constraint exists and is violated.

select and textarea

<select> matches :invalid if it is required and its current value is an empty string (a common pattern is a "-- choose --" placeholder <option value=""> at the top). <textarea> follows the same rules as <input> for required, minlength, and maxlength.

Firefox and :-moz-ui-invalid

Firefox has long applied styles via :-moz-ui-invalid, which only activates after user interaction — effectively the :user-invalid behavior built in. If you add your own :invalid rules and test in Firefox, the field may look fine (because the browser's default user-interaction guard is on), then behave differently in Chrome (where the default guard is off). Define explicit rules and use :user-invalid with a fallback to get consistent behavior.

Accessibility

Color alone is never sufficient for communicating an error — users with color-vision deficiencies may not perceive a red border. Pair :invalid styling with:

  • A visible text message explaining what went wrong and how to fix it.
  • An icon or symbol alongside the color change (e.g., a ✕ or warning icon).
  • aria-invalid="true" on the control so screen readers announce it as invalid.
  • aria-describedby pointing at the error message element so the description is read automatically.
<label for="email">Email address</label>
<input
  id="email"
  type="email"
  aria-invalid="true"
  aria-describedby="email-error"
  required
/>
<span id="email-error" role="alert">
  Please enter a valid email address.
</span>

The role="alert" on the error span causes screen readers to announce the message as soon as it appears in the DOM, even without focus.

Pseudo-classMatches when…
:validthe control passes all its constraints
:requiredthe control has the required attribute
:optionalthe control does not have required
:out-of-rangea numeric/date input's value exceeds min/max
:in-rangea numeric/date input's value is within min/max
:placeholderthe placeholder text of an input
:focusthe control currently has keyboard focus

Browser support

:invalid is part of Selectors Level 4 and has been supported in all major browsers for many years. :user-invalid (the interaction-aware variant) shipped in Chrome 119, Firefox 88 (as :-moz-ui-invalid much earlier), and Safari 16.5.

For more on HTML's native constraint attributes, see <input> and HTML Forms.

Practice

Practice
What is the function of the ':invalid' pseudo-class in CSS?
What is the function of the ':invalid' pseudo-class in CSS?
Was this page helpful?