Skip to content

Validate Input After Interaction

Displaying validation errors the moment a user focuses on a field and starts typing is premature and distracting. For example, as a user types an email address (e.g., “user@gm”) or a password with complex requirements, the field is technically invalid until completion. Standard :invalid styling results in an error state appearing immediately, frustrating the user.

The :user-invalid pseudo-class allows you to defer the error state until the user has “committed” to a value (by modifying and blurring the field) or attempted to submit the form. This ensures validation feedback is provided only after the user has finished interacting with the field.

  1. HTML Constraint: DO use standard HTML5 attributes like type="email", pattern, and required to trigger the browser’s built-in validation logic.
  2. Visual Feedback: DO use :user-invalid to apply error styling only after interaction.
  3. Positive Reinforcement: DO optionally use :user-valid to give a green “success” indicator once the requirements are met.
  4. Graceful Recovery: As soon as the user corrects the input to a valid format, :user-invalid stops matching, removing the error state immediately.
  5. Error Text: DO show visible error text with the error styling, so the error isn’t shown by color alone. DO start each message with an error icon that has a text alternative such as “Error” (the safest choice), or word every message as an error, for example “Please enter a valid email address.”, and use the same approach for every error in the form. DO say how to fix the error when you know how, for example “Enter an email address in the format [email protected].”
  6. Error Link: DO list the error message in the field’s aria-describedby, after any hint, only while the field is invalid, so screen readers read it with the field. A reference in the markup would read the hidden message from page load. The script in “Consistent ARIA Experience” does this for each field whose data-error attribute is set to its error message’s id.

MANDATORY: Rely on standard HTML5 attributes for email fields. The error message is hidden by default and only revealed when the browser determines the user has left the field in an invalid state.

<form>
<div class="field">
<label for="email">Email Address</label>
<!-- MANDATORY: Place format hints above the input so autocomplete popovers don't cover them during editing -->
<span id="email-hint" class="hint">Format: [email protected]</span>
<!-- DO: Use standard HTML validation attributes like type="email" and required -->
<!-- Set data-error to the error message's id. The aria-invalid script lists that id in aria-describedby while the field is invalid. -->
<input
type="email"
id="email"
name="email"
required
autocomplete="email"
aria-describedby="email-hint"
data-error="email-error"
>
<div id="email-error" class="error-msg">
<span role="img" aria-label="Error">❌</span> Enter an email address in the format [email protected].
</div>
</div>
</form>
.hint {
display: block;
color: #5f6368;
font-size: 0.85rem;
margin-bottom: 0.25rem;
}
.error-msg {
display: none;
color: #d93025;
font-size: 0.875rem;
margin-top: 0.25rem;
}
/*
DO: Only show error styles after user interaction.
Use multiple indicators (border/background shift + icon/text) to avoid color-only states.
*/
input:user-invalid {
border-color: #d93025;
background-color: #fce8e6;
}
/* DO: Reveal the error message using the adjacent sibling selector */
input:user-invalid + .error-msg {
display: block;
}
/* DO: Provide a clear success indication on :user-valid */
input:user-valid {
border-color: #188038;
}

MANDATORY: Define the complexity rule using a Regex Lookahead pattern in the pattern attribute. The rules list is shown above the input to guide the user, and highlighted if there’s an error.

<form>
<div class="field">
<label for="password">New Password</label>
<!-- MANDATORY: Place hints and rules above the input so mobile keyboards do not obscure them -->
<ul id="password-rules" class="rules-list">
<li>At least 8 characters</li>
<li>One uppercase letter</li>
<li>One lowercase letter</li>
<li>One number</li>
<li>One special character</li>
</ul>
<!-- DO: Use pattern and minlength for complex password validation
DO: Match all constraints with lookaheads via pattern attribute
-->
<!-- Set data-error to the error message's id. The aria-invalid script lists that id in aria-describedby while the field is invalid. -->
<input
type="password"
id="password"
autocomplete="new-password"
required
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\W_]).{8,}"
minlength="8"
aria-describedby="password-rules"
data-error="password-error"
>
<!-- MANDATORY: Give the password its own error text. Turning the rules list red
doesn't say what is wrong, and screen readers don't convey the color change. -->
<div id="password-error" class="error-msg">
<span role="img" aria-label="Error">❌</span> Password doesn't meet all the requirements listed above.
</div>
</div>
</form>
/* DO: State the default styling as neutral */
.rules-list {
color: #5f6368;
margin-bottom: 0.5rem;
}
/* DO: Show invalid state (After interaction): Error */
input:user-invalid {
border-color: #d93025;
background-color: #fce8e6;
}
/* DO: Reveal the error message using the adjacent sibling selector */
input:user-invalid + .error-msg {
display: block;
}
/* DO: Highlight rules list when error is shown using the modern :has() selector */
.field:has(input:user-invalid) .rules-list {
color: #d93025;
font-weight: 600;
}
/* DO: Add success indications for :user-valid state */
input:user-valid {
border-color: #188038;
}
/* DO: Hide rules once satisfied. visibility keeps their space: collapsing the list on
blur would move the submit button between mousedown and mouseup, and the click
would be lost. */
.field:has(input:user-valid) .rules-list {
visibility: hidden;
}

Baseline: user-pseudos

  1. Accessibility:
    • MANDATORY: Use aria-describedby to link the rules list to the input.
    • DO NOT: Hide rules lists entirely until the input is valid; users need to know what to type!
  2. Pattern Attribute Limits: MANDATORY: The pattern attribute performs a full match (implied ^...$). Ensure your password regex accounts for the entire string.
  3. Validation Strictness: DO note that the browser’s default type="email" validation is quite permissive (e.g., user@localserver might pass). If you need stricter validation, you may need to use a more robust validation library or a custom validation function alongside type="email".
  4. Focus Management: MANDATORY: If a user submits the form with an invalid field, the browser will automatically focus the first invalid field. Your :user-invalid styles will apply immediately because a submission attempt counts as an interaction.
  5. Consistent ARIA Experience: Baseline: user-pseudos / aria-invalid

Compatibility notes: Badges like Baseline: … mark web features referenced by this guide. Baseline status describes availability across the core browser set — it does not mean every browser or older version supports the feature. Feature data as published upstream, synced 2026-11-12.

Open the demo standalone · Demo source on GitHub

Counter-example (negative demo) — what this guide tells you to avoid.


Source: GoogleChrome/modern-web-guidance-src (Apache-2.0), upstream commit 179dbc6, synced 2026-11-12. Local changes: macros expanded to links/badges, first heading de-duplicated, demo and this footer added. This is an independent, unofficial site — not a Google publication.