Toegankelijke formulieren en foutmeldingen: praktisch toepassen
Formulieren zijn in de praktijk één van de grootste bronnen van WCAG-fouten: ontbrekende labels, onduidelijke foutmeldingen, slecht focusbeheer en reliance op kleur maken formulieren onbruikbaar voor veel gebruikers. Wij vertalen WCAG-criteria naar concrete code, checklists en teststappen zodat ontwikkelaars, UX-ers en redacties direct kunnen ingrijpen.
Dit artikel laat stap-voor-stap zien hoe je labels, foutsamenvattingen, ARIA, focusmanagement, keyboard- en screenreader-gedrag goed implementeert. Probeer tegelijk je pagina met onze WCAG checker/validator of installeer onze plugin — en mail ons via het contactformulier, we reageren binnen 24 uur.
Het probleem in de praktijk
Veelvoorkomende praktijkfouten die direct onder WCAG vallen:
- Inputs zonder expliciet label of met placeholder als enige label.
- Foutmeldingen die alleen visueel zijn of niet gekoppeld zijn aan het invoerveld (geen aria-describedby/aria-invalid).
- Geen foutsamenvatting of foutsamenvatting die niet focusbaar is.
- Focusverplaatsing na submit ontbreekt of is onvoorspelbaar.
- Slechte contrast en rely-on-color voor foutindicatie.
Deze fouten zijn vaak eenvoudig te verhelpen met structurele componenten en kleine stukjes JavaScript. Hieronder concrete, testbare oplossingen.
Zo los je dit op in code
1. Altijd een expliciet label
Gebruik <label for="... ">
en zorg dat het id
van het inputveld uniek is. Indien visueel niet gewenst: houd het label in de DOM, maar maak het visueel verborgen met een toegankelijke utility-class.
<label for="email" class="sr-only">E-mailadres</label>
<input id="email" name="email" type="email" autocomplete="email" required>
CSS voor toegankelijke visueel verborgen labels
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
2. Verbind foutmeldingen met aria-describedby
Maak voor elk veld een foutcontainer die een id krijgt en zet aria-describedby
op het inputveld. Zet aria-invalid="true"
als het veld ongeldig is.
<input id="username" name="username" aria-describedby="username-error" />
<div id="username-error" class="error" aria-live="polite"></div>
3. Foutsamenvatting bovenaan (met focus)
Bij submit: toon een foutsamenvatting met links naar elk foutveld. Zet focus op de samenvatting zodat screenreaders direct de problemen aankondigen.
<div id="error-summary" class="error-summary" role="alert" aria-labelledby="error-summary-title" tabindex="-1" hidden>
<h2 id="error-summary-title">Er zijn fouten in het formulier</h2>
<ul id="error-list"></ul>
</div>
JavaScript: genereren van foutsamenvatting en focus
function showErrors(form, errors){
const summary = form.querySelector('#error-summary');
const list = form.querySelector('#error-list'); list.innerHTML='';
Object.keys(errors).forEach(name=>{
const msg = errors[name];
const field = form.querySelector('[name="'+name+'"]');
const id = field.id || ('field-'+name);
field.id = id;
field.setAttribute('aria-invalid','true');
const errId = id+'-error';
let errEl = form.querySelector('#'+errId);
if(!errEl){
errEl = document.createElement('div');
errEl.id = errId;
errEl.className='error';
errEl.setAttribute('aria-live','polite');
field.insertAdjacentElement('afterend', errEl);
}
errEl.textContent = msg;
field.setAttribute('aria-describedby', errId);
const li = document.createElement('li');
const a = document.createElement('a');
a.href = '#'+id;
a.textContent = msg;
a.addEventListener('click', e => { e.preventDefault(); field.focus(); });
li.appendChild(a); list.appendChild(li);
});
summary.hidden = false;
summary.focus();
}
4. Client- en server-side validatie
Valideer altijd server-side en geef bij fouten dezelfde semantiek terug (error-summary, aria-describedby, status 400). Client-side validatie is voor directe feedback en betere UX, maar mag server-side niet vervangen.
// voorbeeld client-side validatie tijdens submit
form.addEventListener('submit', e=>{
e.preventDefault();
const errors = {};
const email = form.querySelector('[name="email"]').value;
if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) errors.email='Vul een geldig e-mailadres in';
if(Object.keys(errors).length){ showErrors(form, errors); return; }
// doe fetch naar server, behandel server-errors gelijk
});
5. Focusstijl en keyboard
Zorg voor zichtbare focusstijlen en dat foutlinks in de summary via keyboard bereikbaar zijn. Gebruik overzichtelijke CSS voor focus en hover.
:focus{outline:3px solid #005A9C; outline-offset:2px}
.error{color:#a80000; background:#fff0f0; padding:6px; border-radius:4px}
Checklist voor developers
- Elk inputveld heeft een
<label>
in de DOM (geen placeholder als enige label). - Inputs gebruiken
id
,name
,autocomplete
, en indien relevantinputmode
enpattern
. - Foutmeldingen zijn gekoppeld via
aria-describedby
en inputs krijgenaria-invalid="true"
. - Er is een focusbare foutsamenvatting boven het formulier met
role="alert"
/tabindex="-1"
en manuele focus verplaatsing na submit. - Fouten onderscheiden niet alleen met kleur: voeg iconen en tekst toe, en zorg voor voldoende contrast (AA/AAA).
- Server responses bevatten semantisch gelabelde fouten zodat progressive enhancement werkt.
- Keyboard-navigatie: alle foutlinks zijn focusable en verplaatsen focus naar het relevante veld.
- Automatische tests: axe/pa11y/our WCAG checker integreren in CI.
Gebruik onze WCAG checker/validator om snel te zien welke punten ontbreken en download onze plugin voor directe browserfeedback. Nog vragen? Vul het contactformulier in — we reageren binnen 24 uur.
Tips voor designers en redacties
Labeling en instructies
Schrijf heldere, korte labels en zet aanvullende instructies onder het label (niet als placeholder). Gebruik voorbeelden in de helptekst en markeer verplichte velden expliciet (niet alleen met * zonder uitleg).
Visuele fouten en kleurgebruik
Maak foutstates makkelijk onderscheiden: gebruik rood + icoon + duidelijke tekst. Controleer contrast van fouttekst en achtergrond met onze checker of UI-tooling.
Microcopy voor fouten
Gebruik actiegerichte foutteksten: “Vul een geldig e-mailadres in” i.p.v. “Fout”. Geef indien mogelijk herstelstappen en voorbeelden.
Hoe test je dit?
Handmatige teststappen (developer/UX)
- Open formulier en tab door alle velden heen: elke focus moet zichtbaar zijn.
- Verstuur zonder invoer: foutsamenvatting moet verschijnen en focus krijgen.
- Klik of tab naar fouten in de summary: focus moet naar het corresponderende veld verplaatsen.
- Probeer keyboard-only: alle acties (submit, openen/sluimeren van fouten) werken zonder muis.
- Test met schermlezer (NVDA + Firefox, VoiceOver + Safari): de foutsamenvatting moet gelezen worden, en aria-describedby moet de fout relateren.
- Controleer contrast met onze WCAG checker/validator en download de plugin voor inline checks.
Automatische tests (CI)
// voorbeeld met axe-core en jest
const {configureAxe, toHaveNoViolations} = require('jest-axe');
expect.extend(toHaveNoViolations);
test('form accessibility', async ()=>{
const html = renderFormHtml(); // server-side render of component
const results = await axe(html);
expect(results).toHaveNoViolations();
});
Of gebruik pa11y voor integratietests: npx pa11y https://jouw-site.nl/formulier
. Vergeet niet ook onze WCAG checker in CI te draaien voor een snel overzicht van issues.
Screenreader quick checks
- NVDA: focus naar form en Submit; luister naar de foutsamenvatting.
- VoiceOver: controleer leesvolgorde en link-navigatie binnen foutsamenvatting.
Heb je weinig tijd? Gebruik onze WCAG checker/validator om direct issues te vinden. Installeer de plugin voor realtime feedback tijdens development. Vragen? Contactformulier — antwoord binnen 24 uur.
Praktische mini-how-to: server-response voor form errors
// JSON response bij 400 Bad Request (voorbeeld)
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"errorSummary": "Er zijn invoerfouten",
"errors": {
"email": "Vul een geldig e-mailadres in",
"password": "Wachtwoord moet minimaal 8 tekens bevatten"
}
}
Render deze response op dezelfde manier als client-side errors: vul error-list en zet aria-invalid/aria-describedby. Dit houdt de UX consistent en toegankelijk.
Tools en integratie
Gebruik deze combinatie voor hoog rendement:
- Lokale development: onze WCAG plugin (download op wcagtool.nl/plugin) voor live feedback.
- CI: axe-core of pa11y + onze WCAG checker/validator voor rapportage.
- Manual: NVDA/VoiceOver + keyboard tests volgens bovenstaande stappen.
Test je website direct met onze WCAG checker/validator (https://wcagtool.nl/validator). Vraag hulp via het contactformulier als je wilt dat wij meekijken — binnen 24 uur reactiegarantie.
Laatste praktische tip
Voeg deze korte sanity-check script toe aan je pagina om snel te verifiëren dat foutvelden correct gelinkt zijn (run in console of CI):
(function(){
document.querySelectorAll('input,textarea,select').forEach(f=>{
const described = f.getAttribute('aria-describedby');
if(f.hasAttribute('aria-invalid') && (!described || !document.getElementById(described))){
console.warn('Toegankelijkheidsprobleem:', f.name||f.id, 'aria-invalid zonder aria-describedby of ontbrekende error-element');
}
});
})();
Direct testen? Gebruik onze WCAG checker/validator of download de plugin; of stuur ons je vraag via het contactformulier — we reageren binnen 24 uur.