Das Problem
Bei obligatorischen Drop-down-Menüs (z.B. „Land auswählen“) wird das Feld bei der Standardvalidierung sofort als ungültig gekennzeichnet, wenn die Standardoption einen leeren Wert hat. Das kann zu visuellem Rauschen führen. Der Fehler soll nur angezeigt werden, wenn der Nutzer das Menü öffnet und schließt, ohne eine Option auszuwählen, oder wenn er versucht, das Formular zu senden.
Die Lösung
Die Pseudoklasse :user-invalid funktioniert nahtlos mit <select>-Elementen. Der Interaktionsfluss des Nutzers wird berücksichtigt: Das einfache Laden der Seite oder das Fokussieren/Unfokussieren ohne Änderung zählt nicht als Interaktion. Das Feld bleibt also neutral, bis der Nutzer aktiv versucht, eine Auswahl zu treffen.
Implementierungsstrategie
- HTML-Einschränkung: Verwenden Sie ein
<select>mitrequired. Die erste Option solltevalue=""enthalten und idealerweise deaktiviert/ausgeblendet sein, um eine gültige Auswahl zu erzwingen. - Visuelles Feedback: Verwenden Sie
:user-invalid, um den Rahmen des Auswahlfelds zu gestalten. - Zeitpunkt: Der Browser betrachtet das Feld als „interagiert“, wenn der Nutzer den Wert ändert (auch zurück in den ungültigen Standardzustand), bevor er das Steuerelement verlässt, oder wenn das Formular gesendet wird.
Implementierungsleitfaden
1. HTML-Struktur
Die Option „placeholder“ ist hier entscheidend.
<form>
<div class="field">
<label for="country">Country</label>
<select
id="country"
name="country"
required
aria-errormessage="country-error"
>
<option value="" disabled selected>Select a country...</option>
<option value="us">United States</option>
<option value="ca">Canada</option>
<option value="uk">United Kingdom</option>
</select>
<div id="country-error" class="error-msg">
Please select a country.
</div>
</div>
</form>
2. CSS
.error-msg {
display: none;
color: #d93025;
font-size: 0.875rem;
margin-top: 0.25rem;
}
/*
Only show error after the user visits the select menu.
*/
select:user-invalid {
border-color: #d93025;
background-color: #fce8e6;
}
select:user-invalid + .error-msg {
display: block;
}
select:user-valid {
border-color: #188038;
}
Fallback und Browserunterstützung
Fallbacks und Browserunterstützung für :user-valid und :user-invalid
CSS für Fallback
input:user-invalid,
input.user-invalid-fallback {
border-color: #d93025;
background-color: #fce8e6;
}
input:user-invalid + .error-msg,
input.user-invalid-fallback + .error-msg {
display: block;
}
JavaScript-Fallback
Verwenden Sie ein wiederverwendbares Dienstprogramm, das den Interaktionsstatus mithilfe eines WeakMap verfolgt. So wird verhindert, dass das DOM mit „schmutzigen“ Klassen oder Datenattributen verunreinigt wird.
const UserInvalidFallback = (() => {
const dirtyState = new WeakMap();
const updateState = (input) => {
const isValid = input.checkValidity();
// Update both visual and ARIA state
input.classList.toggle('user-invalid-fallback', !isValid);
input.classList.toggle('user-valid-fallback', isValid);
if (!isValid) {
input.setAttribute('aria-invalid', 'true');
} else {
input.removeAttribute('aria-invalid');
}
};
const handleEvent = (event) => {
const input = event.target;
if (event.type === 'reset') {
const controls = input.elements || [];
for (const control of controls) {
dirtyState.delete(control);
control.classList.remove('user-invalid-fallback');
control.classList.remove('user-valid-fallback');
control.removeAttribute('aria-invalid');
}
return;
}
if (!input.checkValidity) return;
if (event.type === 'input' || event.type === 'change') {
const state = dirtyState.get(input) || { hasInteracted: false, hasBlurred: false };
state.hasInteracted = true;
dirtyState.set(input, state);
if (state.hasBlurred) {
updateState(input);
}
} else if (event.type === 'blur') {
const state = dirtyState.get(input) || { hasInteracted: false, hasBlurred: false };
state.hasBlurred = true;
dirtyState.set(input, state);
if (state.hasInteracted) {
updateState(input);
}
}
};
const init = (root = document) => {
if (CSS.supports('selector(:user-invalid)')) return;
root.addEventListener('blur', handleEvent, true); // Capture phase
root.addEventListener('input', handleEvent);
root.addEventListener('change', handleEvent);
root.addEventListener('reset', handleEvent, true); // Capture resets
};
return { init };
})();
// Initialize for a specific form
const form = document.querySelector('#demo-form');
UserInvalidFallback.init(form);
Weitere Überlegungen
- Verhalten auf Mobilgeräten: Auf Mobilgeräten kann die Unkenntlichmachung je nach Betriebssystemauswahl unterschiedlich erfolgen. Es wird empfohlen, die Tests auf echten Geräten durchzuführen.
- Barrierefreiheit im Internet: Native
:user-invalid-Elemente werden nicht automatisch mit ARIA-Attributen synchronisiert. Fügen Sie den folgenden JavaScript-Code hinzu, umaria-invalidmit dem visuellen Status synchron zu halten:
// Sync aria-invalid with the CSS :user-invalid state
const syncAria = (el) => {
el.setAttribute?.('aria-invalid', el.matches(':user-invalid') ? 'true' : 'false');
};
// Update on blur (to show error) and input (to clear it)
document.addEventListener('blur', (e) => syncAria(e.target), true);
document.addEventListener('input', (e) => {
if (e.target.hasAttribute('aria-invalid')) syncAria(e.target);
});