Toegankelijke formulieren en foutmeldingen: direct toepassen
Formulieren zijn in de praktijk een van de grootste bronnen van WCAG-fouten: ontbrekende labels, ontoegankelijke foutmeldingen, slechte focusbeheer en onduidelijke ARIA-implementaties zorgen voor onbruikbaarheid voor mensen met screenreaders en toetsenbordgebruikers. Ontwerpers en developers negeren vaak edge-cases (inline fouten, file uploads, datepickers) waardoor conversie en toegankelijkheid tegelijk kelderen.
Wij bij WCAGtool.nl maken formulieren wél bruikbaar: duidelijke patronen, testbare code-snippets en stappenplannen die je meteen kunt inzetten. Test je pagina direct met onze WCAG checker/validator, download onze plugin via wcagtool.nl/plugin en vraag advies via ons contactformulier — we reageren binnen 24 uur.
Het probleem in de praktijk
Veelvoorkomende fouten
- Labels ontbreken of zijn niet gekoppeld met for/id.
- Foutmeldingen zijn visueel maar niet programmeerbaar (geen aria-live, geen aria-describedby).
- Geen foutoverzicht met focusverschuiving naar het eerste probleem.
- Custom widgets (datepickers, fileuploaders) missen keyboard support en ARIA roles.
- ARIA-attributen verkeerd gebruikt: aria-hidden op verkeerde elementen, aria-live op statische content.
Waarom dit vaak fout gaat
Developers vertrouwen op visuele styling en client-side libraries zonder de ARIA- en semantische fallback te implementeren. Designers leveren visueel duidelijkere fouten, maar vergeten beschrijvende teksten voor screenreaders en redacties plaatsen onduidelijke foutfraseringen.
Zo los je dit op in code
1) Basispatroon: correcte label-associatie
Altijd for + id of een wrapping label gebruiken. Voor radio/checkbox groepen: use fieldset + legend.
<label for="email">E-mailadres</label><br><input id="email" name="email" type="email" required aria-describedby="emailHelp"><br><small id="emailHelp">Gebruik je werk- of privé-e-mail</small>
2) Inline foutmelding met aria-describedby en aria-invalid
Markeer invalid en koppel fouttekst via aria-describedby. Zorg dat fouttekst zichtbaar en programmeerbaar is.
<input id="phone" name="phone" type="tel" aria-invalid="true" aria-describedby="phone-error"><br><span id="phone-error" role="alert">Voer een geldig telefoonnummer in</span>
3) Foutoverzicht bovenaan (error summary) met focus management
Toegankelijke pattern: verzamel server- of client-side errors en toon een samenvatting boven het formulier. Zet focus op die samenvatting en geef links/anchors naar individuele velden.
<div id="error-summary" role="alert" tabindex="-1"><h2>Er zijn fouten in het formulier</h2><ul><li><a href="#email">E-mailadres ontbreekt</a></li></ul></div>
4) Focus verplaatsen programatisch (JS)
Verplaats focus naar de error-summary zodra formulier validatie faalt.
document.getElementById('form').addEventListener('submit',function(e){if(!this.checkValidity()){e.preventDefault();const summary=document.getElementById('error-summary');summary.focus();}});
5) Keyboard en ARIA voor custom widgets (datepicker voorbeeld)
Voor custom components: role, aria-expanded, aria-controls, keyboard handlers en roving tabindex.
<div class="datepicker" role="application"><button aria-haspopup="dialog" aria-expanded="false" id="dp-toggle">Open datumkiezer</button><div id="dp-dialog" role="dialog" aria-hidden="true">...kalender...",
6) CSS voor zichtbare focus (gebruik :focus-visible)
:focus-visible{outline:3px solid #ff9800;outline-offset:2px;}button:focus-visible{box-shadow:0 0 0 3px rgba(255,152,0,0.25);}
7) Server-side validatie en ARIA
Vertrouw nooit alleen op client-side. Stuur errors terug in JSON met field identifiers en genereer server-rendered error-summary en aria-describedby koppelingen. Voor SPAs: synchroniseer serverfouten naar de DOM en trigger focus op de error-summary.
// voorbeeld response: { "errors": { "email":"Ongeldig e-mailadres" } }
Checklist voor developers
- Elke input heeft een label (for/id) of een ingesloten label.
- Gebruik fieldset + legend voor logische groepen (radio/checkbox).
- Voeg aria-describedby toe voor hulp- en foutteksten.
- Gebruik aria-invalid=”true” op foutieve velden.
- Toon een error-summary met role=”alert” en tabindex=”-1″ en verplaats daar focus na submit.
- Zorg dat custom widgets keyboard-accessible zijn en correcte ARIA roles/attributes gebruiken.
- Gebruik
:focus-visiblevoor duidelijke focusstijlen zonder visuele ruis. - Valideer altijd ook server-side en mappen server-fouten terug naar toegankelijke UI.
- Automatiseer checks met onze WCAG checker/validator en installeer onze plugin in je CI.
Tips voor designers en redacties
Schrijf duidelijke foutteksten
Formuler foutmeldingen actiegericht: “Voer een geldig e-mailadres in” is beter dan “Ongeldige invoer”. Voeg voorbeelden en acceptatiecriteria toe (bijv. formaat, lengte).
Design voor tekstuitbreiding
Houd rekening met langere foutteksten en vertalingen — test met +30% tekstlengte. Zorg dat foutboxen niet overlappen met andere content.
Visuele hiërarchie en kleurgebruik
Kleur alleen gebruiken als extra signaal. Combineer rood met icon en tekst. Zorg contrast >4.5:1 voor foutteksten en labels; controleer met onze contrasttool.
Hoe test je dit?
Automatisch scannen
- Gebruik onze WCAG checker/validator voor een eerste scan (WCAG 2.1 AA checks + praktische aanbevelingen).
- Plaats onze plugin in je CI: elke PR krijgt meteen toegankelijkheidsfeedback.
Handmatige checks (stap-voor-stap)
- Schakel alle CSS uit? Controleer label-to-field koppelingen met browser devtools.
- Toets formulier volledig uit met alleen toetsenbord: tab, shift+tab, enter, space; controleer zichtbare focus en keyboard operability van custom widgets.
- Gebruik een schermlezer (NVDA/VoiceOver): lees labels, helpteksten en foutmeldingen hardop; activeer invalide velden en verifieer dat errors aankondigen (aria-live/role=”alert”).
- Simuleer server-side fouten: stuur ongeldige data en controleer error-summary focus en links naar velden.
- Controleer contrast met onze validator en test tekstuitbreiding/zoom (200%).
Testscript dat je kunt gebruiken
// sneltest: keyboard + screenreader basics (voeg toe aan QA run)await page.goto('https://jouwsite.nl/form');await page.keyboard.press('Tab'); // alle interactables doorlopenawait expect(page).toHaveFocus('#email'); // controleer focuspositieawait page.click('#submit');await expect(page.locator('#error-summary')).toBeVisible();await expect(page.locator('#error-summary')).toHaveFocus();
Concrete mini-how-to’s
Hoe maak je een toegankelijke file upload
<label for="file">Upload CV</label><input id="file" name="file" type="file" aria-describedby="file-help file-error"><div id="file-help">PDF of DOC, max 5MB</div><div id="file-error" role="alert"></div>
Controleer bestandsgrootte en mime-type client- & server-side; toon fout in file-error en geef focus naar error-summary bij submit.
Hoe implementeer je aria-live voor realtime validatie
<div id="live-error" aria-live="polite" aria-atomic="true"></div>function showLiveError(msg){document.getElementById('live-error').textContent=msg;}
Voorbeeld volledige validatie (HTML + JS)
<form id="contact"><label for="name">Naam</label><input id="name" name="name" required><label for="email">E-mail</label><input id="email" name="email" type="email" required><button type="submit">Verstuur</button><div id="error-summary" role="alert" tabindex="-1" style="display:none"><h2>Er zijn fouten</h2><ul id="error-list"></ul></div></form><script>document.getElementById('contact').addEventListener('submit',function(e){e.preventDefault();const errors=[];const name=document.getElementById('name');const email=document.getElementById('email');if(!name.value.trim())errors.push({id:'name',msg:'Vul je naam in'});if(!/^\S+@\S+\.\S+$/.test(email.value))errors.push({id:'email',msg:'Ongeldig e-mailadres'});const summary=document.getElementById('error-summary');const list=document.getElementById('error-list');list.innerHTML='';if(errors.length){errors.forEach(err=>{const li=document.createElement('li');const a=document.createElement('a');a.href='#'+err.id;a.textContent=err.msg;li.appendChild(a);list.appendChild(li);const field=document.getElementById(err.id);field.setAttribute('aria-invalid','true');field.setAttribute('aria-describedby','error-'+err.id);let span=document.getElementById('error-'+err.id);if(!span){span=document.createElement('span');span.id='error-'+err.id;span.setAttribute('role','alert');field.insertAdjacentElement('afterend',span);}span.textContent=err.msg;});summary.style.display='block';summary.focus();return false;}else{summary.style.display='none';this.submit();}});</script>
Meer tools en hulp
Gebruik onze online validator voor snelle scans en gedetailleerde uitleg per issue. Installeer de CI-plugin om regressies te voorkomen. Voor projectadvies of hands-on implementatie kun je ons team bereiken via het contactformulier — wij reageren binnen 24 uur.
Test je formulier nu meteen: ga naar wcagtool.nl/validator, plak je URL en ontvang directe verbeterpunten.
Laatste praktische tip
Voeg dit korte snippet toe onderaan je formulier om altijd een toegankelijke error-summary te tonen en focus te zetten wanneer validatie faalt:
function showErrorSummary(errors){const s=document.getElementById('error-summary')||document.createElement('div');s.id='error-summary';s.setAttribute('role','alert');s.tabIndex=-1;s.innerHTML='<h2>Er zijn fouten</h2><ul>'+errors.map(e=>'<li><a href=\"#'+e.id+'\">'+e.msg+'</a></li>').join('')+'</ul>';document.querySelector('form').prepend(s);s.focus();}
Nog vragen? Test nu met onze WCAG checker/validator, download de plugin en neem contact op via ons contactformulier — antwoord binnen 24 uur.