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.