Het probleem
Standaard HTML5-validatie biedt visuele feedback (via :invalid of :user-invalid), maar wordt niet automatisch gesynchroniseerd met toegankelijkheidsattributen zoals aria-invalid.
Als je de standaard :invalid-stijl gebruikt, kunnen schermlezers 'Ongeldige invoer' aankondigen op het moment dat een gebruiker naar een verplicht veld gaat dat op dat moment leeg is. Dit is storend voor gebruikers die ondersteunende technologieën gebruiken, omdat de fout wordt aangekondigd voordat er interactie heeft plaatsgevonden.
De oplossing
We willen dat de programmatische status (aria-invalid="true") op exact hetzelfde moment wordt toegepast als de visuele status (:user-invalid). Omdat :user-invalid afhankelijk is van de interne markering 'user-interacted' van de browser, kunnen we JavaScript gebruiken om te controleren of deze kiezer overeenkomt tijdens standaard interactiegebeurtenissen.
Ga naar MDN aria-invalid voor meer informatie.
Implementatiestrategie
- Visuele laag: Gebruik CSS
:user-invalidom randen/iconen te tonen. - Toegankelijkheidslaag: Gebruik
aria-invalidenaria-errormessageom de status door te geven aan ondersteunende technologie (AT). - Visuele en toegankelijkheidslaag overbruggen: Maak een lichte JavaScript-utility die luistert naar
blur- eninput-gebeurtenissen, controleert of het element overeenkomt met:user-invaliden de ARIA-kenmerken dienovereenkomstig updatet.
Implementatiehandleiding
1. HTML-structuur
Koppel je invoer aan de foutmelding met aria-errormessage (of aria-describedby voor bredere ondersteuning).
<form>
<div class="field">
<label for="email">Email</label>
<input
type="email"
id="email"
required
aria-errormessage="email-error"
>
<span id="email-error" class="error-msg">
Please enter a valid email address.
</span>
</div>
</form>
2. CSS
Beheer de zichtbaarheid van de foutmelding met de native pseudoklasse :user-invalid.
.error-msg {
display: none;
color: #d93025;
}
/* Show error message when input is user-invalid */
input:user-invalid ~ .error-msg {
display: block;
}
/* Optional: Visual cues on the input itself */
input:user-invalid {
border-color: #d93025;
}
3. JavaScript
Omdat er geen gebeurtenis UserInvalidChanged is, koppelt u aan standaard formuliergebeurtenissen om de status te controleren.
const updateAriaState = (event) => {
const input = event.target;
if (!input.matches?.('input, textarea, select')) return;
// Check if the browser currently considers this input "user-invalid"
const isUserInvalid = input.matches(':user-invalid');
if (isUserInvalid) {
input.setAttribute('aria-invalid', 'true');
} else {
input.removeAttribute('aria-invalid');
}
};
// Listen on the document to handle dynamically added fields.
// 'blur' and 'focus' do not bubble, so we must use the capture phase (true).
document.addEventListener('blur', updateAriaState, true);
document.addEventListener('focus', updateAriaState, true);
// Also update on input if we've already shown the error,
// so the error clears immediately when fixed.
document.addEventListener('input', (event) => {
const input = event.target;
if (!input.matches?.('input, textarea, select')) return;
const hasAriaInvalid = input.hasAttribute('aria-invalid');
const ariaInvalid = input.getAttribute('aria-invalid');
if (hasAriaInvalid && ariaInvalid === 'true') {
updateAriaState(event);
}
});
Terugval en browserondersteuning
Functiedetectie
Je kunt controleren op ondersteuning in CSS en JavaScript.
JavaScript-controle:
if (!CSS.supports('selector(:user-invalid)')) {
// Fallback logic here
}
CSS voor reserve
Als je wilt dat je fallback-logica visueel niet te onderscheiden is van het native gedrag, moet je je foutstijlen toepassen op zowel de pseudoklasse als je fallback-klasse.
/* Apply error styles to both native selector and fallback class */
input:user-invalid,
input.user-invalid-fallback {
border-color: #d93025;
background-color: #fce8e6;
}
/* Show error message for both cases */
input:user-invalid ~ .error-msg,
input.user-invalid-fallback ~ .error-msg {
display: block;
}
Reserve-logica
Als :user-invalid ontbreekt, houd je de interactiestatus handmatig bij met een WeakMap.
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' && input.matches?.('form')) {
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.matches?.('input, textarea, select')) 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 = () => {
if (CSS.supports('selector(:user-invalid)')) return;
document.addEventListener('blur', handleEvent, true); // Capture phase required
document.addEventListener('input', handleEvent, true);
document.addEventListener('change', handleEvent, true);
document.addEventListener('reset', handleEvent, true); // Capture resets
};
return { init };
})();
// Initialize globally
UserInvalidFallback.init();
Andere overwegingen
aria-livetegenaria-errormessage:aria-errormessagekoppelt de invoer aan de tekst, maar schermlezers kondigen deze misschien niet meteen aan wanneer deze verschijnt (alleen wanneer de focus op de invoer ligt).- Als je een onmiddellijke aankondiging nodig hebt wanneer de fout verschijnt (bijvoorbeeld bij vervaging), kun je overwegen
role="alert"ofaria-live="polite"toe te voegen aan de container voor foutmeldingen, maar test dit grondig om een 'dubbele aankondiging' te voorkomen wanneer de gebruiker het veld activeert om het probleem op te lossen.
Internationalisering:
- Zorg dat de tekstcontent van je foutmelding (
#email-error) is vertaald. De logica blijft hetzelfde.
- Zorg dat de tekstcontent van je foutmelding (