Using CSS :valid and :invalid for Form Validation

The CSS :valid and :invalid pseudo-classes provide a native, browser-driven way to style HTML form elements based on whether their contents meet specific validation requirements. This guide covers how these pseudo-classes integrate with HTML5 constraint validation, how to apply them in your stylesheets, practical implementation examples, and modern techniques to prevent common user-experience pitfalls like showing error states before a user has finished typing.

Understanding the Validation Mechanism

HTML5 introduced built-in client-side validation using attributes such as required, pattern, type="email", type="url", min, max, and maxlength. When a user interacts with a form field containing these attributes, the browser evaluates the input against the defined rules.

The :valid and :invalid pseudo-classes automatically target these form controls:

  • :valid: Matches any form element whose current value satisfies all of its validation constraints. It also matches optional elements that have no constraints or empty non-required fields.
  • :invalid: Matches any form element whose current value fails one or more validation constraints.

These selectors can be applied to <input>, <select>, <textarea>, and <form> elements.

Basic Syntax and Implementation

You can target inputs directly to apply visual indicators such as borders, background colors, or icons:

/* Style for valid inputs */
input:valid {
  border-color: #2e7d32;
}

/* Style for invalid inputs */
input:invalid {
  border-color: #d32f2f;
}

When applied to a <form> element, :invalid allows you to style the form container or disable submission styles if any child input fails validation:

/* Visual cue if the form contains any invalid inputs */
form:has(:invalid) button[type="submit"] {
  opacity: 0.6;
  cursor: not-allowed;
}

The "Immediate Error" Problem

A major drawback of using raw :invalid styles is that elements with the required attribute are considered invalid by the browser the moment the page loads. Showing red borders or error messages on empty, untouched fields creates a frustrating user experience.

Solution 1: Combine with :not(:placeholder-shown)

To suppress the invalid state on empty fields until the user starts typing, combine :invalid with placeholder checks:

/* Only show invalid state once the user has entered text */
input:not(:placeholder-shown):invalid {
  border-color: #d32f2f;
}

Solution 2: Modern Alternative with :user-invalid

Modern browsers now support the :user-invalid and :user-valid pseudo-classes. Unlike standard :invalid, :user-invalid only applies after the user has interacted with the input (such as typing and blurring the field), preventing premature error states on initial page load:

input:user-valid {
  border-color: #2e7d32;
}

input:user-invalid {
  border-color: #d32f2f;
}

Accessibility Considerations

Relying entirely on color changes to indicate validation states creates accessibility barriers for visually impaired users. When using :valid and :invalid, always:

  • Pair color changes with text labels, icons, or error messages.
  • Ensure text labels are associated with inputs via aria-describedby.
  • Maintain adequate color contrast ratios between input borders and background colors.