Het ontstaan van WCAG: van 1.0 naar 2.2

Keyboard & focus management volgens WCAG — praktische implementatie | wcagtool.nl

Keyboard en focusmanagement: praktisch toepassen (WCAG)

In de praktijk faalt keyboardtoegankelijkheid vaak door ontbrekende skip-links, verkeerde tabindex-order, ontbrekende focus-styles en slecht beheerde modals. Dat leidt tot frustratie voor toetsenbordgebruikers en faalt WCAG-criteria zoals 2.1.1 (Keyboard) en 2.4.7 (Focus Visible).

Wij lossen dit door concrete, testbare stappen te geven: werkende HTML-snippets, duidelijke CSS voor focus, herbruikbare JavaScript-functies voor focus-trapping/restoratie en checklists die je meteen op je project kunt toepassen. Test met onze WCAG checker, download onze plugin en neem contact op via het contactformulier — wij reageren binnen 24 uur.

Het probleem in de praktijk

Ontbrekende of onzichtbare focus, onnatuurlijke tabvolgorde en modals die focus ‘vangen’ zonder terug te geven zijn de grootste oorzaken. Veel teams vertrouwen alleen op automatische tools en vergeten keyboard- en screenreader-tests. Dit leidt tot oplossingen die technisch scoren maar niet echt gebruiksvriendelijk zijn.

Voorbeelden van vaak voorkomende fouten

  • Link of button zonder zichtbare :focus-staat; gebruiker weet niet waar de focus is.
  • Elementen met tabindex=”0″ en tabindex=”-1″ verkeerd toegepast — breaks de natuurlijke leesvolgorde.
  • Modals die niet focussen op het eerste focusbare element of die toetsenbordfocus naar de achtergrond toestaan.
  • Skip-links die onzichtbaar zijn of niet goed werken op mobiele schermen.

Zo los je dit op in code

1) Voorgedefinieerde skip-link (HTML + CSS)

Voeg altijd een skip-link toe als eerste focusbaar element in de body:

<a href="#main" class="skip-link">Sla navigatie over</a>
<!-- later in de pagina -->
<main id="main">…</main>
.skip-link {position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden;}
.skip-link:focus, .skip-link:active {position:static;left:auto;width:auto;height:auto;clip:auto;background:#fff;color:#000;padding:8px 12px;z-index:1000;}

2) Duidelijke focus-styles — gebruik :focus-visible

Gebruik moderne selectors en een gefallbackte CSS-variant voor oudere browsers.

:focus-visible {outline:3px solid #ffbf47;outline-offset:3px;}
/* fallback voor browsers zonder :focus-visible */
:focus {outline:3px solid rgba(255,191,71,.25);outline-offset:3px;}
button, a, [tabindex]:not([tabindex='-1']) {outline-offset:3px;}

3) Tabindex-regels: houd het simpel

Regel: vermijd positieve tabindex-waarden (>0). Gebruik tabindex=”0″ om niet-focusable elementen focusable te maken en tabindex=”-1″ om focus via script toe te wijzen.

<div role="button" tabindex="0" aria-pressed="false">Toggle</div>  /* keyboard toegankelijk */
element.focus();  /* als je via JS wilt focussen: element.setAttribute('tabindex','-1'); element.focus(); */

4) Toegankelijke modal (HTML + minimale JS)

Belangrijke punten: trap focus binnen modal, zet achtergrond aria-hidden, focus eerste focusbaar element en restore focus bij sluiten.

<!-- HTML -->
<button id="openModal">Open modal</button>
<div id="modal" role="dialog" aria-modal="true" aria-hidden="true" aria-labelledby="modalTitle">
  <h2 id="modalTitle">Titel</h2>
  <button class="close">Sluit</button>
  <a href="#">Link</a>
</div>
// JS: focus trap & toggle (eenvoudige, testbare implementatie)
function getFocusable(el){return Array.from(el.querySelectorAll('a[href],button,textarea,input,select,[tabindex]:not([tabindex=\"-1\"])')).filter(n=>!n.hasAttribute('disabled'));}
function openModal(modal, trigger){document.body.querySelectorAll('main,header,footer,nav').forEach(n=>n.setAttribute('aria-hidden','true'));modal.removeAttribute('aria-hidden');const focusable=getFocusable(modal);focusable[0]?.focus();modal.dataset.triggerId=trigger.id;document.addEventListener('keydown',trap);function trap(e){if(e.key==='Tab'){const first=focusable[0], last=focusable[focusable.length-1];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'){closeModal(modal);}}}
function closeModal(modal){document.body.querySelectorAll('main,header,footer,nav').forEach(n=>n.removeAttribute('aria-hidden'));modal.setAttribute('aria-hidden','true');const trigger=document.getElementById(modal.dataset.triggerId);trigger?.focus();document.removeEventListener('keydown',trap);}
document.getElementById('openModal').addEventListener('click',e=>openModal(document.getElementById('modal'),e.currentTarget));document.querySelector('#modal .close').addEventListener('click',()=>closeModal(document.getElementById('modal')));

5) ARIA en semantics: gebruik roles alleen als nodig

Geef de voorkeur aan native elementen (button, a, form). Gebruik ARIA als aanvulling, niet als vervanging van semantiek.

<button aria-expanded="false" aria-controls="menu">Menu</button>  /* update aria-expanded via JS */
/* Vermijd: div role="button" zonder keyboard handlers */

Checklist voor developers

  • Voeg skip-link toe en test: druk Tab vanaf paginabegin en activeer skip.
  • Zorg dat alle interactieve elementen een zichtbare focus-staat hebben (:focus-visible).
  • Gebruik geen tabindex>0 — houd je aan tabindex=”0″ en “-1”.
  • Beperk ARIA: gebruik native elementen waar mogelijk.
  • Bij modals: trap focus, zet achtergrond aria-hidden, restore focus bij sluiten, sluit op Escape.
  • Test keyboard-navigatie volledig: Tab, Shift+Tab, Enter, Space, Escape.
  • Automatiseer checks met onze WCAG checker en installeer de plugin voor CI-integratie.

Tips voor designers en redacties

Ontwerp voor zichtbare focus

Ontwerp component-states inclusief focus. Maak focus-contrasten minimaal even sterk als hover-states. Gebruik duidelijke randen of achtergrondveranderingen en test in de UI-kit.

Content-structuur en tabvolgorde

Zorg dat content-logica overeenkomt met tabvolgorde: plaats belangrijke content eerder in DOM dan visuele sidebars wanneer tabvolgorde belangrijk is. Redacties: gebruik semantische elementen (h1..h6, nav, main, article).

Component-bibliotheek: lever ARIA-standaarden

Lever voor elk component (dropdown, modal, tabs) een toegankelijke referentie-implementatie met testcases en focusgedrag beschreven.

Hoe test je dit?

Handmatig: keyboard-only test

  1. Laad pagina, druk Tab vanaf paginabegin — kom je bij skip-link?
  2. Gebruik Tab/Shift+Tab door alle interactieve controls; volgende elementen moeten logisch zijn.
  3. Open modals, controleer dat focus binnen modal blijft; sluit met Escape en controleer restore focus.
  4. Controleer forms: labels gekoppeld, errorfocus werkt.

Screenreader-test

Gebruik NVDA of VoiceOver: navigeer met headings, links en landmarks; verifieer dat aria-labels en roles kloppen.

Automatisch + onze tool

Draai onze WCAG checker voor baseline issues en integreer de plugin in CI. Gebruik de tool ook na fixes om regressies te detecteren.

Sneltest scripts

// voorbeeld van een kleine Node.js script-check met puppeteer voor keyboard tab-order (concept)
const puppeteer=require('puppeteer');(async()=>{const b=await puppeteer.launch();const p=await b.newPage();await p.goto('https://jouwsite.nl');await p.keyboard.press('Tab');console.log('Eerste focus:',await p.evaluate(()=>document.activeElement.outerHTML));await b.close();})();

Extra resources & calls-to-action

Test je site nu direct met onze online WCAG checker. Download onze plugin voor automatische scans in je CI. Vragen over implementatie? Gebruik ons contactformulier — we antwoorden binnen 24 uur.

Probeer meteen: voer je homepage in op de checker en voer deze checklist punten af op één pagina per sessie.

Laatste praktische tip (copy-paste-ready)

Plak dit kleine helper-script in je project om op één plek consistente focus-styles en een skip-link te garanderen:

<!-- Add in <body> direct na opening -->
<a href=\"#main\" class=\"skip-link\">Sla navigatie over</a>
<style>.skip-link{position:absolute;left:-9999px}.skip-link:focus{position:static;left:auto;background:#fff;color:#000;padding:6px 10px;z-index:9999}*:focus{outline-offset:3px}:focus-visible{outline:3px solid #ffbf47}</style>
<script>document.addEventListener('click',e=>{if(e.target.matches('[data-open-modal]')){const id=e.target.getAttribute('data-open-modal');const modal=document.getElementById(id);document.body.querySelectorAll('main,nav,header,footer').forEach(n=>n.setAttribute('aria-hidden','true'));modal.removeAttribute('aria-hidden');modal.querySelector('button, a, input, [tabindex]:not([tabindex=\"-1\"])')?.focus();}});</script>

Nog vragen of wil je dat wij je implementatie reviewen? Gebruik ons contactformulier — antwoord binnen 24 uur. Test direct je site met de WCAG checker en download de plugin voor CI-integratie.