Form State Pseudo-Classes (:checked, :disabled, :valid)
HTML form controls carry a lot of built-in state — checked or not, enabled or not, required or not, passing HTML5 validation or not. CSS exposes all of this directly as pseudo-classes, so you can style a form's current state without touching JavaScript.
The pseudo-classes
Pseudo-class | Matches when... |
|---|---|
| A checkbox or radio (or a |
| A form control has the |
| A form control is capable of being interacted with (the default state). |
| A field has the |
| A field does NOT have the |
| A field's current value passes HTML5 validation constraints. |
| A field's current value fails HTML5 validation constraints. |
| A numeric/date field's value is within its |
| A numeric/date field's value falls outside its |
:checked
input[type="checkbox"]:checked {
accent-color: #2563eb;
}
/* Style a custom "toggle" label based on the hidden checkbox's state */
input.toggle:checked + label {
background: #22c55e;
}:disabled / :enabled
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
input:enabled:hover {
border-color: #2563eb;
}:required / :optional and :valid / :invalid
/* Mark required fields visually */
input:required {
border-left: 3px solid #f59e0b;
}
/* Numeric range field */
input[type="number"] {
border: 1px solid #ddd;
}
input[type="number"]:out-of-range {
border-color: crimson;
background: #fff5f5;
}Worked example: styling invalid input, carefully
A naive approach shows a red border the instant an input is invalid — which usually means it flashes red before the user has even typed anything, since an empty required field is technically :invalid.
/* Naive: shows red immediately on page load */
input:invalid {
border-color: crimson;
}A common improvement is to only flag invalid state once the field has actually been interacted with, using a class toggled by JavaScript on blur:
/* Only show the error state after the field has been
touched (a JS-toggled class), not on first render */
input.touched:invalid {
border-color: crimson;
background: #fff5f5;
}
input.touched:valid {
border-color: #22c55e;
}