Toegankelijkheid testen: handmatig of met tools?

Praktische implementatie van WCAG — focus, labels, ARIA en toetsen

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)

  1. Tab door de pagina: alle interactieve items moeten zichtbaar focusen en volgorde logisch zijn.
  2. Schakel afbeeldingen uit of zet alt-tekst controles: schermlezers vereisen relevante alt-teksten.
  3. Verwijder styles tijdelijk (browser devtools) om te controleren of de HTML-structuur betekenisvol is.
  4. 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