How to highlight errors in a form
Short answer
The :user-invalid pseudo-class fires when the user entered a wrong value and left the field:
<style>
input { padding: 8px; border: 2px solid silver; border-radius: 6px; }
input:user-invalid { border-color: crimson; }
input:user-valid { border-color: seagreen; }
</style>
<input type="email" placeholder="Email" required>Type anna, then click outside the field — the border turns red. Fix it to anna@example.com — it turns green.
An error message
The hint is kept hidden and shown when the field next to it becomes invalid. The :has() pseudo-class checks the field inside the label:
<style>
.error { display: none; color: crimson; font-size: 14px; }
label:has(:user-invalid) .error { display: block; }
</style>
<label>
Email <input type="email" required>
<span class="error">Enter an email like anna@example.com</span>
</label>What "invalid" means
A field is invalid if it breaks its constraints: required, type, pattern, minlength, min, max. So first describe the rules with attributes, and CSS only shows the result of the check.
Common mistake
The older :invalid pseudo-class is used. It fires right on load, so an empty required form greets the user with red borders before they have typed anything:
<style>
input:invalid { border: 2px solid crimson; }
</style>
<input type="email" placeholder="Already red" required>Functions used in this recipe
- :user-invalid — Matches an invalid field only after the user has interacted with it; `:user-valid` is the opposite.
- :valid — `:valid` matches a field whose value passes the built-in checks; `:invalid` matches one that does not.
- :has() — Matches an element that contains something matching the selector in brackets — the "parent selector".
- required — Makes a field required: the form will not submit while it is empty.
- <input> — An input field; its kind is set by the `type` attribute.