Toegankelijke formulieren: praktisch toepassen (labels, validatie, focus, ARIA)
Formulieren falen in de praktijk vaak door kleine missers: ontbrekende of verkeerd gekoppelde labels, onzichtbare foutmeldingen, slechte focus-flow en reliance op kleur of placeholder-tekst als enige hint. Dat maakt invoer onbruikbaar voor mensen met schermlezers, toetsenbordgebruikers en bezoekers met cognitieve of motorische beperkingen.
Wij vertalen WCAG-voorwaarden naar concrete code, checklist en testcases die je direct kunt toepassen. Hieronder vind je stap-voor-stap oplossingen met HTML/ARIA/CSS/JS-snippets, testinstructies en praktische checks — inclusief een korte workflow om je site direct met onze WCAG checker/validator te scannen.
Het probleem in de praktijk
1) Ontbrekende of fout gekoppelde labels
Veel inputs gebruiken placeholders in plaats van echte labels of missen het id/for-koppel. Schermlezers en spraakgestuurde invoer hebben een expliciete label-associatie nodig.
2) Foutmeldingen zijn niet zichtbaar of niet vindbaar
Fouttekst die alleen met kleur wordt aangegeven of onder het veld staat zonder aria-descriptions en focus verliest, wordt gemist door schermlezers en toetsenbordgebruikers.
3) Onjuiste focus management bij validatie
Na submit verplaatst de focus niet naar de eerste fout: gebruikers weten niet wat ze moeten corrigeren. Asynchrone validatie zonder live region zorgt dat schermlezers niets melden.
4) Custom controls zonder keyboard/ARIA
Custom dropdowns, toggles en datepickers missen vaak role, keyboard handlers en focus states — ze zijn onbruikbaar zonder muis.
Zo los je dit op in code
Basis: altijd een label koppelen
<label for="email">E-mail</label>
<input id="email" name="email" type="email" required aria-describedby="email-help">
<small id="email-help">We sturen nooit je e-mailadres door</small>
Belangrijk: gebruik id op input en for op label. Gebruik name voor server-side verwerking.
Gebruik native HTML-validatie en verrijk met ARIA
<input id="phone" name="phone" type="tel" pattern="^\+?\d{7,15}$" aria-describedby="phone-help phone-error">
<div id="phone-error" role="alert" aria-live="assertive" class="error" hidden>Voer een geldig telefoonnummer in</div>
Stel aria-live of role=”alert” in voor asynchrone meldingen. aria-describedby linkt hulp- of fouttekst aan input.
Server-side foutmeldingen: focus naar eerste fout
// Minimal JS pattern: focus eerste fout na server response
const errors = document.querySelectorAll('.field-error');
if (errors.length) {
errors[0].querySelector('input,select,textarea').focus();
errors[0].querySelector('.error').removeAttribute('hidden');
}
Zorg dat server responses consistent HTML renderen met dezelfde ids en classes zodat de client-side focus logica werkt.
Voorbeeld volledig formulier — toegankelijk en testbaar
<form id="signup" novalidate>
<div class="form-row">
<label for="name">Volledige naam</label>
<input id="name" name="name" type="text" required aria-required="true" aria-describedby="name-help name-error">
<small id="name-help">Vul je officiële naam in</small>
<div id="name-error" class="error" role="alert" aria-live="assertive" hidden></div>
</div>
<fieldset>
<legend>Abonnement</legend>
<div>
<input type="radio" id="monthly" name="plan" value="monthly" required>
<label for="monthly">Maandelijks</label>
</div>
<div>
<input type="radio" id="yearly" name="plan" value="yearly">
<label for="yearly">Jaarlijks</label>
</div>
</fieldset>
<button type="submit">Aanmelden</button>
</form>
Client-side validatie: aria-invalid en aria-describedby updaten
function showError(input, message) {
const id = input.id + '-error';
const err = document.getElementById(id);
err.textContent = message;
err.removeAttribute('hidden');
input.setAttribute('aria-invalid', 'true');
input.setAttribute('aria-describedby', (input.getAttribute('aria-describedby') || '') + ' ' + id);
}
function clearError(input) {
const id = input.id + '-error';
const err = document.getElementById(id);
err.textContent = '';
err.setAttribute('hidden', '');
input.removeAttribute('aria-invalid');
}
Toegankelijke custom toggle (voorbeeld)
<div role="switch" aria-checked="false" tabindex="0" id="newsletter-toggle">Nieuwsbrief: uit</div>
<script>
const toggle = document.getElementById('newsletter-toggle');
function setState(state) {
toggle.setAttribute('aria-checked', String(state));
toggle.textContent = 'Nieuwsbrief: ' + (state ? 'aan' : 'uit');
}
toggle.addEventListener('click', () => setState(toggle.getAttribute('aria-checked') !== 'true'));
toggle.addEventListener('keydown', (e) => {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
setState(toggle.getAttribute('aria-checked') !== 'true');
}
});
</script>
Voeg role, aria-checked en keyboard handlers toe voor volledige keyboard-toegankelijkheid.
Visuele focus: gebruik .focus-visible
:focus { outline: none; }
:focus-visible { outline: 3px solid #1a73e8; outline-offset: 2px; }
Laat normale gebruikersstijl ongemoeid, maar toon duidelijke focus voor toetsenbordgebruikers. Gebruik CSS-trucjes niet als enige aanwijzing.
Checklist voor developers
- Koppel elk form-element aan een <label for=”…”/> of gebruik aria-label/aria-labelledby waar nodig.
- Gebruik fieldset & legend voor radios/checkbox groepen.
- Voor foutmeldingen: gebruik role=”alert” of aria-live=”assertive” en zet focus op eerste fout.
- Markeer valideerbare velden met required/aria-required en aria-invalid bij fouten.
- Voeg aria-describedby toe voor hulp- en fouttekst zodat screenreaders de context lezen.
- Voor custom controls: implementeer role, tabindex, aria-* attributen en volledige keyboard handling.
- Gebruik visuele en tekstuele aanwijzingen, niet alleen kleur of placeholders.
- Test met toetsenbord, schermlezer (NVDA/VoiceOver) en onze WCAG checker/validator.
- Gebruik progressive enhancement: native controls eerst, JS als verbetering.
Tips voor designers en redacties
Formulieren ontwerpen met toegankelijkheid in gedachten
Plaats labels boven inputs of left-aligned; vermijd placeholders als enige label. Geef heldere, korte instructies en plaats deze direct bij het veld. Gebruik consistente volgorde en maak foutmeldingen kort en actiegericht (wat is fout en wat moet je doen).
Kleur en contrast
Gebruik tekst en iconen naast kleurcodes (geen kleur alléén). Zorg dat foutteksten en randkleuren voldoen aan contrast-eisen (minimaal WCAG AA). Test buttons en form controls in verschillende states (hover, focus, disabled).
Redactie: microcopy voor instructies en fouten
Schrijf foutmeldingen vanuit actie: “Vul een geldig e-mailadres in zoals naam@voorbeeld.nl” i.p.v. “Foutief e-mailadres”. Houd consistentie in toon en lengte zodat schermlezers geen lange zinnen moeten voorlezen.
Hoe test je dit?
Handmatige teststappen (core)
- Verwijder de muis: navigeer alleen met Tab/Shift+Tab. Check of alle controls bereikbaar en logisch geordend zijn.
- Verstuur formulier met lege/incorrecte waarden. Controleer of de focus naar de eerste fout springt en of foutmeldingen worden aangekondigd door een schermlezer.
- Controleer radio/checkbox groepen met fieldset/legend en of labels klikbaar zijn.
- Gebruik schermlezer (NVDA op Windows, VoiceOver op macOS/iOS): controleer of labels, help- en foutteksten correct voorgelezen worden.
- Controleer met kleurenblindheid-simulatie en dat foutindicaties niet alleen op kleur vertrouwen.
Automated tests: axe-core voorbeeld
// Node/Playwright of Puppeteer snippet met axe-core
const { AxePuppeteer } = require('@axe-core/puppeteer');
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://jouwsite.nl/formulier');
const results = await new AxePuppeteer(page).analyze();
console.log(results.violations);
await browser.close();
})();
Run deze test in CI en configureer tolerate-rules voor bekende cases (maar los echte problemen op, geen suppress).
Gebruik onze WCAG checker/validator
Scan je pagina direct op WCAG-issues met onze WCAG checker/validator op wcagtool.nl/checker. Voor browser-geïntegreerde checks download je onze plugin (beschikbaar op wcagtool.nl/plugin). Heb je vragen? Gebruik ons contactformulier — we beantwoorden binnen 24 uur.
Quick tests die je nu kunt doen
- Tab door formulier en noteer volgorde.
- Verwijder alle labels in de DOM (voor test) en check welke velden misdraaien — dit toont ontbrekende label-dependenties.
- Schakel CSS uit: controleer of formulier nog steeds bruikbaar is zonder visuele layout.
Laatste praktische tip
Gebruik dit kleine script om direct na submit te focussen op de eerste fout en een aria-alert te laten voorlezen — plak dit in je page scripts en pas ids aan:
(function(){
document.getElementById('signup')?.addEventListener('submit', function(e){
e.preventDefault(); // alleen voor demo, in productie: verzend na validatie
const invalid = this.querySelector('[aria-invalid="true"], .error:not([hidden])');
if (invalid) {
const field = invalid.querySelector('input,select,textarea') || invalid;
field.focus();
// screenreader announce via role=alert element (zorg dat error zichtbaar is)
const announcer = document.getElementById('form-announcer') || (function(){
const a = document.createElement('div');
a.id = 'form-announcer';
a.setAttribute('aria-live', 'assertive');
a.setAttribute('role', 'status');
a.style.position = 'absolute';
a.style.left = '-9999px';
document.body.appendChild(a);
return a;
})();
announcer.textContent = 'Er zijn fouten in het formulier. Beweeg naar het gemarkeerde veld om ze te corrigeren.';
} else {
this.submit(); // of fetch voor AJAX
}
});
})();
Test deze flow meteen en scan daarna je pagina met onze WCAG checker/validator op wcagtool.nl/checker. Download onze plugin voor snelle on-page checks en neem contact op via ons contactformulier — vragen worden binnen 24 uur beantwoord.