De overstap van WCAG 2.0 naar 2.1: wat veranderde er?

Keyboard-toegankelijkheid & focusbeheer: praktische WCAG-implementatie

Keyboard-toegankelijkheid en focusbeheer zijn in de praktijk vaak de zwakste schakels in WCAG-implementatie: ontwikkelaars gebruiken custom controls zonder juiste focus, designers verwijderen outlines, en modals of dropdowns ‘vangen’ focus of breken tabvolgorde. Dat leidt tot onbruikbare interfaces voor toetsenbord- en schermlezergebruikers.

Wij lossen dit op met concrete, testbare stappen, herbruikbare code-snippets en checklists die je direct kunt inzetten in je project. Test direct je site met onze WCAG checker/validator, installeer onze plugin via plugin download en stel vragen via contact — reacties binnen 24 uur.

Het probleem in de praktijk

Veelvoorkomende fouten die je snel herkent:

  • Gebruik van non-semantic elementen (div/span) als knoppen zonder tabindex/role/keyboard handling.
  • Tabindex-misbruik: te veel tabindex=”0″ of negatieve tabindexwaardes die volgorde breken.
  • Focus-beeld is verwijderd (outline: none) zonder alternatief.
  • Modals, dialogs of custom dropdowns zonder focus trap of focus terugzetten bij sluiten.
  • Composite widgets (tabs, radiogroepen, menubuttons) zonder roving tabindex of ARIA-acties.

Voorbeeldsymptoom: custom knop met div

Veel code bevat iets als: <div class="btn" onclick="doX()">Opslaan</div> Dit is niet bereikbaar met toetsenbord en wordt niet aangekondigd door schermlezers.

Zo los je dit op in code

Gebruik altijd semantiek eerst

Als het element een knop is, gebruik <button>. Als het een link is, gebruik <a href=”…”>. Pas alleen JS aan wanneer semantiek niet volstaat.

Voorbeeld – vervang slechte code door semantiek:

<!-- slecht --><div class="btn" onclick="save()">Opslaan</div><br><!-- goed --><button class="btn" type="button" onclick="save()">Opslaan</button>

Tabindex: regels en snippet

Regels: vermijd tabindex>0, gebruik tabindex=”0″ alleen om element in tabvolgorde op te nemen, gebruik tabindex=”-1″ om programmatically focus te zetten.

Snippet – focus programmatically zonder tabbable maken:

function focusElement(el){el.setAttribute('tabindex','-1');el.focus();el.addEventListener('blur',()=>el.removeAttribute('tabindex'),{once:true});}

Focus-visible: toon focus op de juiste momenten

Gebruik :focus-visible in plaats van outline: none. Polyfill niet nodig in moderne browsers, fallback via class ‘js-focus-visible’.

/* CSS */button:focus-visible{outline:3px solid #005fcc;border-radius:3px;}button:focus{outline:none;}/* fallback via JS add class when keyboard used */

Toegankelijke modal met focus trap (HTML + JS)

Basis-HTML modal (voorbeeld):

<button id="openModal">Open modal</button><div id="modal" role="dialog" aria-modal="true" aria-labelledby="mTitle" hidden><h2 id="mTitle">Modal</h2><button id="closeModal">Sluiten</button></div>

Focus-trap JS (testbare, compacte versie):

const openBtn=document.getElementById('openModal');const modal=document.getElementById('modal');const closeBtn=document.getElementById('closeModal');function getFocusable(el){return Array.from(el.querySelectorAll('a[href],button:not([disabled]),input,select,textarea,[tabindex]:not([tabindex="-1"])')).filter(e=>e.offsetParent!==null);}openBtn.addEventListener('click',()=>{const prev=document.activeElement;modal.hidden=false;const focusables=getFocusable(modal);const first=focusables[0];const last=focusables[focusables.length-1];first.focus();function trap(e){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()}}function close(){modal.hidden=true;document.removeEventListener('keydown',trap);prev.focus()}closeBtn.addEventListener('click',close,{once:true});document.addEventListener('keydown',trap);});

Roving tabindex voor composite widgets (tabs / custom radio)

Gebruik één tabbable element en beheer intern focus met arrow keys. Kort voorbeeld voor arrow navigation:

const items=document.querySelectorAll('.tab');let idx=0;items.forEach((it,i)=>{it.setAttribute('tabindex',i===0?0:-1);it.addEventListener('keydown',e=>{if(e.key==='ArrowRight'){items[idx].setAttribute('tabindex',-1);idx=(idx+1)%items.length;items[idx].setAttribute('tabindex',0);items[idx].focus();}if(e.key==='ArrowLeft'){items[idx].setAttribute('tabindex',-1);idx=(idx-1+items.length)%items.length;items[idx].setAttribute('tabindex',0);items[idx].focus();}})});

ARIA: wanneer en hoe

ARIA is een aanvulling op semantische HTML, geen vervanging. Gebruik role, aria-expanded, aria-controls en aria-modal voor dynamische widgets. Voorbeeld dropdown:

<button aria-haspopup="true" aria-expanded="false" id="menuBtn">Menu</button><ul id="menu" role="menu" hidden><li role="menuitem"><a href="#">Item 1</a></li></ul>

Checklist voor developers

  • Gebruik semantische elementen: button, a, input, select, etc.
  • Vermijd tabindex>0; gebruik tabindex=”-1″ voor programmatic focus.
  • Implementeren van :focus-visible of zichtbare focusstijlen.
  • Voeg keyboard handlers toe (Enter/Space/Escape/Arrow) alleen waar semantiek dit niet afdekt.
  • Zorg voor focus trap in modals en zet focus terug naar trigger bij sluiten.
  • Implementeer roving tabindex in composite widgets (tabs/menugroups/radiogroepen).
  • Gebruik ARIA correct: role, aria-expanded, aria-controls, aria-hidden waar nodig.
  • Test met alleen-toetsenbord, schermlezer en onze WCAG checker/validator.

Tips voor designers en redacties

Ontwerp zichtbare focusstaten

Definieer in je designsystem duidelijke focusstijlen (kleur, dikte, afstand). Vermijd complete verwijdering van outlines. Zorg dat focuscontrast voldoet aan kleurcontrastregels.

Labels & interactief gedrag

Tekstredacteurs: gebruik duidelijke, korte labels en linktekst. Vermijd ‘klik hier’. Controleer dat interactieve elementen altijd tekst of aria-label hebben.

Componentbibliotheken

Maak toegankelijke componenten standaard: modals, dropdowns, tabs en accordions met ingebouwde keyboard-logica en ARIA. Verspreid voorbeelden en code-snippets in Storybook of componentdocs.

Hoe test je dit?

Handmatige toetsenbordtest (stap-voor-stap)

  1. Schakel muis uit of leg hand op toetsenbord.
  2. Tab door de pagina en controleer logische volgorde.
  3. Activeer alle interactieve controls (Enter/Space) en controleer gedrag.
  4. Open modals/dropdowns: focus moet naar eerste meaningful element springen.
  5. Probeers de Tab/Shift+Tab cyclus binnen modals—je mag de pagina erachter niet bereiken.
  6. Sluit modal met Escape en controleer dat focus terugkeert naar trigger.

Automated tests en tools

Gebruik axe-core in CI of browser-extensie, Lighthouse en onze WCAG checker/validator voor snelle audits. Integreer accessibility linting (eslint-plugin-jsx-a11y) voor React/Vue.

Screenreader-check

Test met NVDA/JAWS (Windows) en VoiceOver (macOS/iOS). Controleer dat aria-labels, roles en live regions correct worden aangekondigd.

Sneltest met onze tool

Ga naar wcagtool.nl/checker, plak je URL en ontvang direct een rapport met prioriteitsissues en concrete code-oplossingen. Installeer onze plugin voor snelle lokale scans: plugin download.

Concrete testbare snippets & commands

DevTools quick check (Console)

Voer deze snippet in Console om focusable elementen op pagina te tonen:

document.querySelectorAll('a[href],button:not([disabled]),input,select,textarea,[tabindex]:not([tabindex="-1"])');

Modal smoke-test snippet

Plak deze code in pagina om een basis modal te proberen (werkt standalone):

/* voeg html toe */const modalHtml='<button id="openModal">Open modal</button><div id="modal" role="dialog" aria-modal="true" aria-labelledby="mTitle" hidden><h2 id="mTitle">Modal</h2><button id="closeModal">Sluiten</button></div>';document.body.insertAdjacentHTML('beforeend',modalHtml);/* voeg JS (zie focus trap eerder) */

CI integratie (axe-core)

Voorbeeld npm script test:

"scripts":{ "a11y:test":"node_modules/.bin/axe https://localhost:3000 --save=axe.json" }

Gebruik onze WCAG checker/validator naast CI voor extra handmatige checks en prioritering.

Praktische tip: test direct je live site nu met onze checker, download de plugin voor snelle lokale audits en stuur vragen via contact — wij reageren binnen 24 uur.

Laatste praktische check – CSS snippet kopiëren en plakken om focus direct zichtbaar en compliant te maken:

/* plak in je main CSS */:focus{outline:none;}:focus-visible{outline:3px solid #005fcc;outline-offset:2px;border-radius:4px;}/* fallback voor oude browsers: voeg class .show-focus via JS wanneer keyboard gebruikt */