Veel implementaties falen op eenvoudige, herhaalbare punten: ontbrekende labels, onnauwkeurige focus-styling, verkeerd gebruikte ARIA en dynamische content zonder focus- of live-regionbeheer. Dat zorgt voor frustratie bij gebruikers met assistive technology en leidt tot onnodige reparatierondes.
Wij vertalen WCAG naar directe, codeerbare stappen: kant-en-klare snippets, testscripts en checklists die developers, UX-designers en redacties direct kunnen toepassen. Test je site meteen met onze WCAG checker/validator, download onze plugin en bij vragen: gebruik het contactformulier — we reageren binnen 24 uur.
Het probleem in de praktijk
Veelvoorkomende fout 1 — ontbrekende of foutieve labels
Inputs zonder expliciete label of met visueel weggewerkte labels (placeholder als enige label) breken schermlezers en keyboard- gebruikers. Oplossing: altijd een <label for>
of aria-label
/aria-labelledby
als er geen zichtbaar label kan zijn.
<!-- Correct: gekoppeld label --><br><label for="email">E-mail</label><br><input id="email" name="email" type="email" required aria-describedby="emailHelp"><br><small id="emailHelp">We delen je e-mail niet.</small>
Veelvoorkomende fout 2 — slechte focus-indicatie
Ontbrekende of onzichtbare focus-states maken keyboardgebruik nagenoeg onmogelijk. Oplossing: consistente focusstijl, contrasterende outline en geen focus verwijderen met outline: none
zonder alternatief.
/* CSS: toegankelijke focusstijl */<br>:focus { outline: 3px solid #ffbf47; outline-offset: 2px; box-shadow: 0 0 0 3px rgba(255,191,71,0.15); }
Veelvoorkomende fout 3 — verkeerd gebruik van ARIA
ARIA is bedoeld om native semantics aan te vullen, niet te vervangen. Gebruik semantische HTML waar mogelijk en ARIA alleen als extra.
Zo los je dit op in code
Skip link: direct toepasbaar
Voeg bovenaan elke pagina een zichtbare skip link toe die zichtbaar wordt bij focus.
<a href="#main" class="skip-link">Spring naar hoofdinhoud</a><br><!-- CSS --><br>.skip-link { position: absolute; left: -999px; top: auto; width: 1px; height: 1px; overflow: hidden; } .skip-link:focus { position: static; left: auto; width: auto; height: auto; }
Toegankelijke knop en role usage
Gebruik <button>
voor interactieve acties, niet <div>
. Voor custom controls: voeg ARIA en keyboard support toe.
<!-- Toegankelijke toggle --><br><button id="menuToggle" aria-expanded="false" aria-controls="mainNav">Menu</button><br><nav id="mainNav" hidden>...</nav><br><script>document.getElementById('menuToggle').addEventListener('click', function(){ const open = this.getAttribute('aria-expanded') === 'true'; this.setAttribute('aria-expanded', String(!open)); document.getElementById('mainNav').hidden = open; });</script>
Modal met focus trap en toegankelijk sluiten
Concrete stappen: 1) verplaats focus naar eerste focusable element, 2) trap focus binnen modal, 3) restore focus bij sluiten, 4) ESC sluit modal.
<!-- HTML --><br><button id="openModal">Open modal</button><br><div id="modal" role="dialog" aria-modal="true" aria-hidden="true"><br><button id="closeModal">Sluit</button><br><input><br></div><br><script>(function(){ const open=document.getElementById('openModal'), modal=document.getElementById('modal'), close=document.getElementById('closeModal'); let lastFocus=null; function trap(e){ const focusable = modal.querySelectorAll('a[href],button,textarea,input,select,[tabindex]:not([tabindex="-1"])'); const first = focusable[0], last = focusable[focusable.length-1]; if(e.key==='Tab'){ if(e.shiftKey && document.activeElement===first){ e.preventDefault(); last.focus(); } else if(!e.shiftKey && document.activeElement===last){ e.preventDefault(); first.focus(); } } if(e.key==='Escape'){ close.click(); } } open.addEventListener('click', ()=>{ lastFocus=document.activeElement; modal.removeAttribute('aria-hidden'); modal.style.display='block'; modal.querySelector('input,button,select,textarea')?.focus(); document.addEventListener('keydown',trap); }); close.addEventListener('click', ()=>{ modal.setAttribute('aria-hidden','true'); modal.style.display='none'; lastFocus?.focus(); document.removeEventListener('keydown',trap); }); })();</script>
Formulieren: errors en aria-describedby
Toon inline errors gekoppeld met aria-describedby
en zet aria-invalid="true"
op velden met fouten.
<label for="name">Naam</label><br><input id="name" name="name" aria-describedby="nameError"><br><div id="nameError" role="alert" class="error">Naam is verplicht</div><br><script>const inp=document.getElementById('name'); if(!inp.value){ inp.setAttribute('aria-invalid','true'); document.getElementById('nameError').style.display='block'; }</script>
Live regions voor dynamische content
Gebruik aria-live
voor asynchrone meldingen en verbind updates expliciet.
<div id="toast" aria-live="polite" aria-atomic="true"></div><br><script>function showToast(msg){ const t=document.getElementById('toast'); t.textContent=''; setTimeout(()=> t.textContent=msg,50); } showToast('Sla succesvol opgeslagen');</script>
Kleurcontrast: CSS-variabelen en toetsen
Gebruik contrastvriendelijke variabelen en test programmatically met een kleine functie.
/* CSS */<br>:root{--color-text:#222;--color-bg:#fff;--accent:#0366d6;}<br>/* JS contrast check (WCAG AA 4.5:1) */<br>function luminance(r,g,b){const a=[r,g,b].map(v=>{v/=255;return v<=0.03928? v/12.92: Math.pow((v+0.055)/1.055,2.4);}); return 0.2126*a[0]+0.7152*a[1]+0.0722*a[2]; } function contrast(hex1,hex2){ const h1=hex1.replace('#',''), h2=hex2.replace('#',''); const r1=parseInt(h1.substr(0,2),16),g1=parseInt(h1.substr(2,2),16),b1=parseInt(h1.substr(4,2),16); const r2=parseInt(h2.substr(0,2),16),g2=parseInt(h2.substr(2,2),16),b2=parseInt(h2.substr(4,2),16); const L1=luminance(r1,g1,b1); const L2=luminance(r2,g2,b2); const ratio=(Math.max(L1,L2)+0.05)/(Math.min(L1,L2)+0.05); return ratio; } console.log('Contrast:', contrast('#222','#fff'));
Checklist voor developers
- Gebruik semantische HTML: buttons, nav, main, header, footer.
- Labels: elk form-element heeft een zichtbaar label of accurate
aria-label
/aria-labelledby
. - Focus: zichtbare, contrasterende focus-states voor alle focusable elementen.
- Keyboard: alle interactive elementen bedienbaar met Tab/Enter/Space/Arrow waar relevant.
- ARIA: alleen toevoegen wanneer native semantics ontbreken; valideer rollen en properties.
- Dynamic UI: focus management +
aria-live
voor updates. - Contrast: check met onze WCAG checker of de JS contrast-functie.
- Automated tests: integreer onze plugin voor CI of gebruik de plugin lokaal.
Tips voor designers en redacties
Design tokens en accessible defaults
Stel kleur- en typografietokens in met toegankelijke basisklassen zodat developers niet per component hoeven te gokken.
/* Voorbeeld tokens */<br>:root{--font-size-base:16px;--line-height:1.5;--primary:#0366d6;--text:#111;--bg:#fff;}
Microcopy en error messaging
Schrijf korte, actiegerichte foutmeldingen; koppel deze aan velden met aria-describedby
. Vermijd vage woorden als "Fout".
Wireframes: markeer interactieve states
Teken hover, focus en disabled states in het ontwerp zodat frontend ze implementeert en testbaar maakt.
Hoe test je dit?
Handmatige teststappen (quick)
- Tab door de pagina: alle interactieve items moeten zichtbaar focusen en volgorde logisch zijn.
- Schakel afbeeldingen uit of zet alt-tekst controles: schermlezers vereisen relevante alt-teksten.
- Verwijder styles tijdelijk (browser devtools) om te controleren of de HTML-structuur betekenisvol is.
- Test forms met toetsenbord: labels, error focus en aria-invalid aanwezig.
Automated checks
Start met onze WCAG checker voor snelle scanning, integreer de plugin in je CI-pipeline voor regressiechecks.
Screenreader test
Luister of de flow klopt: nav headings, links, buttons. Gebruik NVDA/VoiceOver met keyboard-only navigatie.
Concrete testscript (copy-paste)
# Testscript voor ontwikkelaar (console) - run in pagina context<br>(() => { const results = []; // 1. focusable checks<br>document.querySelectorAll('a,button,input,select,textarea,[tabindex]').forEach(el => { const visible = el.offsetWidth||el.offsetHeight||el.getClientRects().length; if(visible && getComputedStyle(el).display!=='none'){ if(!el.matches('a') && el.tagName !== 'BUTTON' && !el.hasAttribute('role') && el.getAttribute('tabindex') === null){ results.push({el, issue:'Possible non-semantic interactive element'}); } } }); console.table(results); return results; })();
Run direct: kopieer het script naar console en los issues op. Wil je niet zelf puzzelen? Gebruik onze online checker en download onze plugin — snel resultaat en integratie.
Tips voor content-editors
Alt-teksten: korte programma's
Schrijf alt-tekst als: context + functie. Voor productfoto's: "Blauwe heren polo, merk X". Voor informatieve afbeeldingen: volledige beschrijving. Voor decoratieve: lege alt alt=""
.
Kopstructuur en scanbaarheid
Gebruik logische heading-hiërarchie (h1→h2→h3). Headings zijn navigatiepunten voor schermlezers.
Links en CTA's
Linktekst moet zelfstandig betekenis hebben: "Lees meer over retourbeleid" in plaats van "Klik hier".
Blijf nooit hangen: test je pagina direct met onze WCAG checker, installeer de plugin voor CMS-integratie en bij vragen gebruik het contactformulier — we reageren binnen 24 uur.
Praktische tip om direct toe te passen: voeg deze one-liner toe aan je build of CI om simpele accessibility regressies te detecteren via onze plugin: npx wcagtool --scan ./dist --report ./accessibility-report.html