Das Problem
Wenn Pflichtfelder sofort beim Seitenaufbau mit einem Fehlerstatus gekennzeichnet werden, kann das verwirrend sein. Idealerweise sollte ein Pflichtfeld nur dann als „ungültig“ angezeigt werden, wenn der Nutzer versucht hat, es auszufüllen, und dabei gescheitert ist.
Die Lösung
Die Pseudoklasse :user-invalid löst dieses Problem perfekt. Bei einem Pflichtfeld wird beim Seitenaufbau keine Übereinstimmung gefunden. Eine Übereinstimmung wird nur in folgenden Fällen erkannt:
- Der Nutzer interagiert mit dem Feld (gibt z.B. ein Zeichen ein und löscht es) und verlässt es dann (Blur), sodass es leer bleibt.
- Der Nutzer versucht, das Formular zu senden, während das Feld leer ist.
Implementierungsstrategie
- HTML-Einschränkung: Fügen Sie Ihren Eingaben das Attribut
requiredhinzu. - Visuelles Feedback: Verwenden Sie
:user-invalid, um den Rahmen rot zu formatieren und den Hilfetext „Erforderlich“ anzuzeigen. - Timing: Verlassen Sie sich auf das native Timing des Browsers für visuelles Feedback. Sie benötigen keine
onBlur-Handler mehr, um einetouched-Klasse hinzuzufügen. Zum Synchronisieren von ARIA-Attributen ist jedoch weiterhin etwas JavaScript erforderlich (siehe unten).
Implementierungsleitfaden
1. HTML-Struktur
<form id="feedback-form">
<div class="field">
<label for="full-name">Full Name</label>
<input
type="text"
id="full-name"
name="full-name"
required
aria-errormessage="name-error"
>
<!-- MANDATORY: Include an icon or distinct non-color indicator alongside error text -->
<div id="name-error" class="error-msg">
<span aria-hidden="true">❌</span> This field is required.
</div>
</div>
</form>
2. CSS
.error-msg {
display: none;
color: #d93025;
font-size: 0.875rem;
margin-top: 0.25rem;
}
/*
Only highlight empty required fields AFTER the user visits them.
MANDATORY: Provide multiple indicators (border shift + helper text/icon) to avoid color-only state communication.
*/
input:user-invalid {
border-color: #d93025;
background-color: #fce8e6;
}
input:user-invalid + .error-msg {
display: block;
}
/* Optional: Subtle indicator for required fields that are valid */
input:required:user-valid {
border-color: #188038;
border-width: 2px;
}
3. JavaScript-Statussynchronisierung
OBLIGATORISCH: Da :user-invalid ein visueller Status ist, MÜSSEN Sie eine JavaScript-Bridge bereitstellen, um aria-invalid="true" dynamisch für Hilfstechnologien zu synchronisieren, wenn ein Nutzer ein ungültiges Feld verlässt oder versucht, das Formular zu senden.
const form = document.getElementById('feedback-form');
const syncAriaInvalid = (input) => {
if (!input.checkValidity()) {
input.setAttribute('aria-invalid', 'true');
} else {
input.removeAttribute('aria-invalid');
}
};
// Sync on blur when a user finishes interacting
form.addEventListener('blur', (e) => {
if (e.target.matches('input[required]')) {
syncAriaInvalid(e.target);
}
}, true);
// Sync all required fields when submission is attempted
form.addEventListener('submit', () => {
form.querySelectorAll('input[required]').forEach(syncAriaInvalid);
});
// Remove error state immediately upon correction
form.addEventListener('input', (e) => {
if (e.target.matches('input[required]') && e.target.checkValidity()) {
e.target.removeAttribute('aria-invalid');
}
});
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
- Sternchen: Es empfiehlt sich weiterhin, Pflichtfelder im Label visuell zu kennzeichnen (z.B. mit einem Sternchen
*), damit Nutzer vor der Interaktion wissen, was sie erwartet. - Buttons zum Senden: Im Gegensatz zu
disabled-Buttons sollten Buttons zum Senden aktiviert bleiben. Wenn der Nutzer darauf klickt, löst der Browser automatisch:user-invalidfür alle leeren Pflichtfelder aus und fokussiert das erste. Das ist hervorragend für die Barrierefreiheit und die UX. - 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);
});