Benutzerfreundliche Formulare: CSS valid, invalid und required
valid und invalid sind ein schneller Hinweis für den Benutzer, dass ein Eingabefeld (noch) nicht korrekt ausgefüllt ist. Sie erkennen anhand von Typ und Attributen wie pattern oder min/max, ob Eingaben akzeptiert werden. Das gilt auch für :required und :checked, die Elemente anhand von Benutzeraktionen oder Zuständen erkennen.
Zustände von Formularfeldern
Ein gutes Dutzend CSS-Pseudoklassen gilt den Zuständen von Formularfeldern wie input, textarea oder select. Sie sollen dem Anwender eine schnelle Rückmeldung beim Ausfüllen von Formularen geben.
Selbst das kleinste Formular kommt dem Benutzer entgegen, wenn die Eingaben schon im Browser des Benutzers geprüft werden.
Die Pseudoklassen :valid, :invalid (erweitert um :user-invalid und user-valid) und :required sind kurz und machen die erste Prüfung einfach.
| Pseudoklasse | Bedeutung |
|---|---|
| :user-valid | Eingabe erfüllt alle Regeln |
| :user-invalid | Regel verletzt |
| :valid | Eingabe erfüllt alle Regeln |
| :invalid | Regel verletzt |
| :placeholder-shown | Platzhalter sichtbar |
:user-invalid und :user-valid sind berechnete Validierungszustände, die zusätzlich den Interaktionsverlauf des Nutzers berücksichtigen.
Diese Pseudoklassen spiegeln konkrete HTML-Attribute oder DOM-Flags wider:
| Pseudoklasse | basiert auf |
|---|---|
| :checked | checked-Status |
| :disabled | disabled-Attribut |
| :enabled | kein disabled |
| :required | required-Attribut |
| :optional | kein required |
| :read-only | readonly |
| :read-write | kein readonly |
:user-invalid vs invalid
Ein Feld ist :invalid, sobald es nicht dem pattern entspricht oder required verletzt oder min / max nicht passt.
Mit :invalid springt die Anzeige sofort auf red (invalid), wenn ein, zwei Zeichen eingegeben wurden. Das ist sehr verwirrend.
<style>
input[name="birthyear"]:user-invalid {
border: 2px solid red;
}
</style>
<label>
Geburtsjahr
<input
type="text"
name="birthyear"
inputmode="numeric"
pattern="^(19\d{2}|20\d{2})$"
title="Bitte ein vierstelliges Jahr eingeben (z. B. 1984)">
</label>
Ein Feld darf nicht schon während des Tippens „rot“ auswerfen, wenn das Format noch gar nicht vollständig sein kann. Das ist ein klassisches Problem bei pattern + :invalid.
:user-invalid (und :user-valid) tauchten schon ca. 2017/2018 in den Editor’s Drafts auf, aber erst seit ~2023 kann :user-invalid bzw. :user-valid Browser-übergreifend eingesetzt werden.
<style>
input[name="birthyear"]:user-invalid {
border: 2px solid red;
}
</style>
<label>
Geburtsjahr
<input
type="text"
name="birthyear"
inputmode="numeric"
pattern="^(19\d{2}|20\d{2})$"
title="Bitte ein vierstelliges Jahr eingeben (z. B. 1984)">
</label>
| Pseudoklasse | Bedeutung |
|---|---|
| :invalid | Formal ungültig laut HTML-Regeln |
| :user-invalid | Vom Benutzer verursachter Validierungsfehler |
:user-invalid hingegen ist kontextsensitiv und »menschlich« und greift nur, wenn das Feld ungültig ist und der Nutzer bereits damit interagiert hat:
- Fokus verloren (blur)
- oder Formular abgeschickt
| Situation | :invalid | :user-invalid |
|---|---|---|
| Seite geladen | ❌ | ❌ |
| Nutzer tippt 18 | ✅ | ❌ |
| Nutzer verlässt Feld | ✅ | ✅ |
| Nutzer tippt 1984 | ❌ | ❌ |
CSS :required
CSS :required reagiert auf das HTML-required-Attribut. Ob Benutzereingaben als korrekt gelten, entscheiden der Typ des Eingabefelds (z.B. type="email") und Attribute wie required, min, max und pattern (regulärer Ausdruck wie [0-9]{5} als Muster für die richtige Eingabe einer Postleitzahl).
<input class="email" type="email" required placeholder="Email (erforderlich)">
input.email:valid {
border: 2px solid green;
background: white !important
}
input.email:focus:invalid {
border: 2px solid red;
}
input.email:required {
background: seashell;
}
:required – Beispiel
elem:required kennzeichnet Felder, in denen das Attribut required gesetzt ist, als erforderliche Eingabe.
- pattern="^\d{5}$" → nur 5-stellige Zahlen erlaubt
- required → Feld darf nicht leer sein
- aria-describedby → barrierefreie Fehleranzeige
<label>
Postleitzahl
<input
type="text"
name="plz"
inputmode="numeric"
pattern="^\d{5}$"
placeholder="z. B. 10115"
required
aria-describedby="plz-error">
<span id="plz-error" class="error">Bitte eine gültige fünfstellige PLZ eingeben</span>
</label>
input[name="plz"] {
border: 2px solid #ccc;
padding: 4px;
border-radius: 4px;
font-size: 1.1rem;
}
/* Fehlerzustand – erst nach Nutzerinteraktion */
input[name="plz"]:user-invalid {
border-color: #c62828;
background-color: #fdecea;
}
input[name="plz"]:user-valid {
border-color: #2e7d32;
background-color: #e8f5e9;
}
/* Fallback für ältere Browser */
input[name="plz"]:invalid:not(:focus):not(:placeholder-shown) {
border-color: #c62828;
}
/* Fehlermeldung anzeigen */
.error {
display: none;
color: #c62828;
font-size: 0.85rem;
}
input:user-invalid + .error,
input:invalid:not(:focus):not(:placeholder-shown) + .error {
display: block;
}
Das pattern-Attribut des input-Tags kann gleich bei der Eingabe signalisieren, ob tatsächlich eine valide Postleitzahl angegeben wird, und zwar ohne Javascript oder serverseitige Programmierung.
Am Rande:
- rein formal sind deutsche PLZ immer nur 5 Ziffern,
- D oder D- ist postalisch optional, eher historisch und etwas angestaubt,
- für Formulare empfohlen: ^\d{5}$
:enabled, :disabled
CSS :disabled wählt Elemente aus, die ein disabled-Attribut gesetzt haben und darum von der Bearbeitung ausgeschlossen sind. Die Browser zeigen Formularfelder mit disabled-Attribut schon von Haus aus ausgegraut oder sehr blass an. CSS :disabled kann darüber hinaus eigene Stile für deaktivierte Formularelemente einbringen.
<input type="text" disabled value="Keine weitere Eingabe"> <select disabled> <option>Kirschen</option> <option>Äpfel</option> <option>Erdbeeren</option> </select>
input:disabled {
background: #eee;
}
select:disabled {
background: #eee
}
Element:checked
Damit der Benutzer gut erkennt, welche Checkboxen bzw. Radio-Buttons gewählt sind, tritt :checked auf den Plan.
<input id="green" type="checkbox"> <label for="green"> Grün </label>
input[type=checkbox]:checked + label {
font-weight: 600;
color: black;
}
Die Farbe von inaktiven Eingabefeldern lässt sich verändern. Checkboxen verändern ihre Position, wenn der Benutzer sie aktiviert – z.B. um aktivierte Checkboxen in einer längeren Serie von Checkboxen hervorzuheben.
HTML
<input id="rot" type="checkbox"> <label for="rot"> Rot </label>
CSS
#name:disabled { background: gainsboro } input[type=checkbox] + label { color: firebrick; } input[type=checkbox]:checked + label { border-bottom: 1px solid green; color: green; }
Mehr zu CSS für Checkboxen und Radio-Buttons:checked
:active und :hover
In CSS und HTML spricht man von einem aktiven Element, wenn ein Benutzer mit einem Element interagiert, meistens durch Anklicken oder Tippen. Das kann ein input-Element sein, in das der Benutzer gerade seinen Namen tippt, ein Button, auf den geklickt wird, aber auch ein Link, der gerade gedrückt wird.
CSS :active betrifft nur Elemente während des Klickens oder Drückens. Die Browser stellen per Vorgabe aktive Elemente heraus, CSS kann eine stärkere Hervorhebung bewirken.
<button id="activebutton" class="btn">Aktivieren</button>
#activebutton:hover {
background: linear-gradient(to top, silver 0%, silver 50%, gainsboro 51%);
color: #666;
}
#activebutton:active {
background: linear-gradient(to top, hsl(185,60%,50%) 0%, hsl(185,60%,50%) 50%, hsl(185,60%,70%) 51%);
color: white;
}
:optional
Die Pseudoklasse :optional ist quasi das Gegenstück zu required: Sie filtert Formularelemente, die nicht ausgefüllt werden müssen.
<label for="name">Name:</label> <input type="text" id="name" required> <label for="email">E-Mail:</label> <input type="email" id="email"> <label for="phone">Telefonnummer:</label> <input type="tel" id="phone">
Das funktioniert allerdings nicht:
input:optional::after {
content: "optional";
}
::after und ::before können nicht direkt auf <input>, <textarea> oder <select>-Elemente angewendet werden. Diese Elemente sind "leere" (self-closing) HTML-Tags und unterstützen keine Pseudo-Elemente.
Also wird das Pseudo-Element ::after auf das label-Element gesetzt:
input:optional ~ label::after {
content: "(optional)";
color: gray;
font-size: 0.8em;
}