Toegankelijke formulieren en foutmeldingen gaan in de praktijk vaak mis: labels ontbreken of zijn visueel weggelaten, error-messaging is niet verbonden met velden, focus wordt niet verplaatst naar fouten en screenreaders krijgen geen realtime feedback. Dat leidt tot onbruikbare formulieren voor toetsenbordgebruikers en mensen met assistieve technologie.
Wij lossen dit door praktische, testbare patterns te leveren: complete HTML+ARIA voorbeelden, CSS-styling voor zichtbare foutstaten, JavaScript voor client-side én server-side validering en duidelijke testinstructies. Test je site direct met onze WCAG checker/validator, download onze plugin en stel vragen via het contactformulier (antwoord binnen 24 uur).
Het probleem in de praktijk
1. Ontbrekende of onjuiste labels
Veel formulieren gebruiken visueel verborgen labels zonder correcte accessible markup of gebruiken placeholders als enige label, wat niet voldoet aan WCAG en onduidelijk is voor screenreaders.
2. Foutmeldingen niet gekoppeld aan velden
Foutmeldingen verschijnen visueel boven het formulier of in een toast, maar zijn niet gekoppeld via aria-describedby of role attributes. Screenreaders en toetsenbordgebruikers missen context.
3. Geen focus management
Na submit blijft focus op de knop of gaat verloren; gebruikers moeten zelf zoeken naar het foutveld. Dat breekt de flow en verhoogt afhaakratio.
Zo los je dit op in code
Basisstructuur van een toegankelijk formulier
Gebruik expliciete <label>-elementen, unieke id’s, aria-describedby voor foutteksten en required/aria-required waar relevant. Voor server-side fouten: voeg role=”alert” of een live region toe en zet focus op de eerste fout.
<form id="signupForm" novalidate><div class="field"><label for="email">E-mailadres</label><input id="email" name="email" type="email" required aria-required="true" aria-describedby="emailError"><div id="emailError" class="error" aria-live="polite"></div></div><div class="field"><label for="password">Wachtwoord</label><input id="password" name="password" type="password" required aria-required="true" aria-describedby="passwordError"><div id="passwordError" class="error" aria-live="polite"></div></div><button type="submit">Aanmelden</button></form>
Client-side validatie en focus management (testbaar)
Voeg JS toe die voorkomt dat het formulier wordt verzonden, valideert per veld, aria-invalid zet en focus naar de eerste fout verplaatst.
document.getElementById('signupForm').addEventListener('submit', function(e){e.preventDefault();const form=this;const fields=['email','password'];let firstInvalid=null;fields.forEach(id=>{const el=document.getElementById(id);const errorEl=document.getElementById(id+'Error');errorEl.textContent='';el.removeAttribute('aria-invalid');if(!el.checkValidity()){el.setAttribute('aria-invalid','true');errorEl.textContent=el.validationMessage;errorEl.setAttribute('role','alert');if(!firstInvalid)firstInvalid=el;}});if(firstInvalid){firstInvalid.focus();return;} // alle velden OK: submit via fetch of form.submit()
Server-side fouten integreren
Als server terugkomt met veldspecifieke fouten, map die naar id’s en update aria-describedby en role=”alert”.
fetch('/api/signup',{method:'POST',body:new FormData(form)}).then(r=>r.json()).then(data=>{if(data.errors){const ids=Object.keys(data.errors);ids.forEach(id=>{const err=document.getElementById(id+'Error');const input=document.getElementById(id);err.textContent=data.errors[id];err.setAttribute('role','alert');input.setAttribute('aria-invalid','true');});document.getElementById(ids[0]).focus();}else{window.location='/welcome'}});
CSS: zichtbare foutstatus en focus
.error{color:#b00020;font-size:0.9rem;margin-top:0.25rem;}input[aria-invalid="true"]{outline:2px solid #b00020;box-shadow:0 0 0 3px rgba(176,0,32,0.12);}input:focus{outline:2px solid #005fcc;}
Checklist voor developers
- Gebruik <label for=”id”> altijd; geen placeholders als enige label.
- Geef elk error-element een id en link met aria-describedby.
- Zet aria-invalid=”true” op ongeldige velden.
- Gebruik role=”alert” of aria-live=”polite” voor foutmeldingen die direct voorlezen mogen worden.
- Verplaats focus programatisch naar het eerste foutveld na submit.
- Test zowel client- als server-side fouten; houd consistente IDs tussen server en client.
- Voeg keyboard-only tests toe: taborder, enter-to-submit, escape to clear modals.
- Automatiseer met axe-core en onze WCAG checker/validator en integreer de plugin in CI.
Tips voor designers en redacties
Visible labels en error messaging
Ontwerp error states die visueel, tekstueel én semantisch gekoppeld zijn aan velden. Gebruik contrastrijke kleuren en iconen, maar rely niet alleen op kleur. Voor redactie: schrijf korte, actiegerichte foutteksten en include suggesties (bijv. “Gebruik een geldig e-mailadres zoals naam@voorbeeld.nl”).
Microcopy en tone
Gebruik heldere instructies boven het veld als extra hulp. Bijvoorbeeld: “Wachtwoord: minimaal 8 tekens, een cijfer en een hoofdletter”. Deze instructies moeten in de DOM staan en gekoppeld via aria-describedby.
Designsystem-inrichting
Leg standaardcomponenten vast: input + label + error container + helper text. Lever code snippets aan devs en documenteer hoe serverfouten gemapped worden naar IDs.
Hoe test je dit?
Handmatige testen (stap-voor-stap)
- Tab door het formulier: labels volgen logisch, volgorde klopt en focus indicator is zichtbaar.
- Laat het formulier leeg en submit: focus gaat naar eerste fout, fouttekst wordt voorgelezen door screenreader.
- Vul één veld fout in (ongeldig e-mail): aria-invalid staat op dat veld en aria-describedby bevat de fout.
- Simuleer serverfout: trigger een 400 met veldfouten en controleer of client DOM updates met role=”alert” en focus.
- Controleer kleurcontrast van foutteksten met onze WCAG checker/validator en in de plugin.
Automatische testen
Integreer axe-core in unit/e2e tests en laat CI falen op kritieke WCAG-issues. Voorbeeld met Jest + axe:
import {render} from '@testing-library/react';import {axe} from 'jest-axe';test('form is accessible', async ()=>{const {container}=render(<SignupForm />);const results=await axe(container);expect(results).toHaveNoViolations();});
Screenreader checks
Gebruik NVDA, VoiceOver of Narrator: na submit moet de foutmelding annunciation zijn. Test ook met keyboard-only en with reduced-motion if transitions are used.
Praktische mini-how-to: server errors naar inputs mappen
Stap 1: server retourneert fouten met veldnamen
{errors:{email:'Ongeldig e-mailadres',password:'Wachtwoord te kort'}}
Stap 2: client map en update DOM
Object.entries(data.errors).forEach(([field,msg])=>{const err=document.getElementById(field+'Error');const input=document.getElementById(field);if(err){err.textContent=msg;err.setAttribute('role','alert');input.setAttribute('aria-invalid','true');}});document.querySelector('[aria-invalid="true"]').focus();
Extra checklist voor content-editors
- Schrijf korte, concrete foutmeldingen met instructies (geen technische jargon).
- Lever helper-teksten en voorbeelden en koppeling naar aria-describedby in het CMS.
- Test content met onze WCAG checker/validator en gebruik de plugin om fouten vroeg te vinden.
Call-to-action en tools
Test je formulier nu met onze WCAG checker/validator op wcagtool.nl en download de plugin om problemen direct in je workflow te zien. Vragen? Gebruik het contactformulier — we reageren binnen 24 uur.
Integratie-tip: automatiseer checks in CI met onze CLI wrapper rond axe/Pa11y en stuur issues naar je backlog als blockers. Wil je hulp? Gebruik ons contactformulier of run direct een scan met de WCAG checker/validator.
Laatste praktische tip: voeg dit kleine JS-snippet toe op alle formulieren om automatisch focus naar de eerste fout te zetten na serverrespons:
function applyServerErrors(errors){const keys=Object.keys(errors);if(!keys.length) return;keys.forEach(k=>{const e=document.getElementById(k+'Error');const i=document.getElementById(k);if(e){e.textContent=errors[k];e.setAttribute('role','alert');i.setAttribute('aria-invalid','true');}});document.getElementById(keys[0]).focus();}
Test direct op wcagtool.nl met onze WCAG checker/validator, download de plugin en stuur vragen via het contactformulier (antwoord binnen 24 uur).