De toekomst van digitale toegankelijkheid wereldwijd

Keyboard & focus: praktische WCAG-implementatie | WCAGtool.nl

Keyboard-navigatie en focusbeheer is één van de meest voorkomende faalpunten bij WCAG-implementatie: interactieve elementen zijn niet bereikbaar met Tab, custom controls missen keyboard handlers, en zichtbare focus-stijlen verdwijnen door CSS-reset. Dat zorgt voor slechte toegankelijkheid in websites, apps en content, en leidt vaak tot onnodige fouten in audits.

Wij van WCAGtool.nl leggen uit hoe je dit praktisch opleest en oplost met concrete stappen, testbare code-snippets en quick-checks. Test direct met onze WCAG checker/validator, download onze plugin voor live feedback of stuur een vraag via het contactformulier — wij antwoorden binnen 24 uur.

Het probleem in de praktijk

In de praktijk zie je deze fouten terug: tabindex>0 wordt gebruikt waardoor tabvolgorde onlogisch wordt; custom elementen (div/span) worden gebruikt zonder role of keyboard-support; focus-indicatoren worden verwijderd door outline:none; modals creëren geen focustrap; en dynamische navigatie (SPA) verplaatst focus niet, waardoor screenreader-gebruikers de context verliezen.

Veelvoorkomende oorzaken

  • Verkeerd of overmatig gebruik van tabindex
  • CSS resets die focus zichtbaar maken onmogelijk maken
  • Custom controls zonder Enter/Space handlers en aria-attributes
  • Dialogs en panels zonder aria-modal/role=”dialog” en zonder focus management
  • Links en knoppen met onduidelijke of contextloze linktekst

Zo los je dit op in code

Baseline: gebruik semantische HTML

Altijd eerst: kies native elementen (button, a, input, select). Pas alleen non-semantic elementen als je ze volledig toegankelijk maakt.

<!-- Gebruik native buttons --><button type="button">Opslaan</button>

Focus zichtbaarheid: gebruik :focus-visible en behoud outline

Voorkom verwijdering van outline; verfijn met :focus-visible voor betere UX.

/* Focus zichtbaar voor toetsenbordgebruikers */:focus-visible{outline:3px solid #005fcc;outline-offset:3px;border-radius:3px;}button:focus-visible, a:focus-visible{box-shadow:0 0 0 3px rgba(0,95,204,0.25);}html{scroll-behavior:smooth;}/* NIET: outline: none; zonder alternatief */

Toegankelijke custom controls: role + keyboard handlers

Als je een custom knop gebruikt, implementeer native keyboard gedrag en aria-attributes.

<div role="button" tabindex="0" aria-pressed="false" id="customBtn">Favoriet</div><script>const btn=document.getElementById('customBtn');btn.addEventListener('click',()=>toggle());btn.addEventListener('keydown',(e)=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();toggle();}});function toggle(){const v=btn.getAttribute('aria-pressed')==='true';btn.setAttribute('aria-pressed',String(!v));}

Modals en focus trap (voorbeeld)

Zet focus in de modal bij open, trap Tab, en herstel focus bij sluiten.

<!-- Open modal --><button id="openModal">Open dialog</button><div id="modal" role="dialog" aria-modal="true" aria-hidden="true"><button id="closeModal">Close</button></div><script>const open=document.getElementById('openModal');const modal=document.getElementById('modal');const close=document.getElementById('closeModal');let lastFocused=null;function trapFocus(e){const focusable='a[href],button,textarea,input,select,[tabindex]:not([tabindex=\"-1\"])';const nodes=Array.from(modal.querySelectorAll(focusable));if(nodes.length===0) return;const first=nodes[0], last=nodes[nodes.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();}}}open.addEventListener('click',()=>{lastFocused=document.activeElement;modal.setAttribute('aria-hidden','false');modal.style.display='block';const focusable='a[href],button,textarea,input,select,[tabindex]:not([tabindex=\"-1\"])';const first=modal.querySelectorAll(focusable)[0];first?.focus();document.addEventListener('keydown',trapFocus);});close.addEventListener('click',()=>{modal.setAttribute('aria-hidden','true');modal.style.display='none';document.removeEventListener('keydown',trapFocus);lastFocused?.focus();});</script>

Tabindex: regels die je direct kunt toepassen

Gebruik tabindex alleen voor: -1 om programmatically te focussen; 0 om element in tabvolgorde te brengen; nooit positieve waarden op normale pagina’s.

<!-- Correct gebruik --><div tabindex=\"-1\" id=\"skipTarget\"></div><a href=\"#skipTarget\" class=\"skip-link\">Naar hoofdinhoud</a>

Skip links en landmarks

Voeg een skip link toe en zorg voor landmarks (main, nav, header, footer).

<a class=\"skip-link\" href=\"#main\">Sla navigatie over</a><main id=\"main\"><h1>Pagina</h1></main>/* CSS: keep visible on focus */.skip-link{position:absolute;left:-999px}.skip-link:focus{left:10px;top:10px;background:#fff;padding:8px;border:2px solid #000;z-index:9999;}

Checklist voor developers

  • Zorg dat alle interactieve items focusbaar zijn met Tab (use native controls waar mogelijk).
  • Gebruik :focus-visible en behoud outline; verwijder outline niet zonder alternatief.
  • Gebruik tabindex=\”-1\” alleen voor programmatic focus, geen tabindex>0.
  • Implementeer Enter en Space voor role=\”button\” en verwachte keyboard shortcuts.
  • Voor modals: role=\”dialog\”, aria-modal=\”true\”, trap focus, restore focus on close.
  • Gebruik landmarks en voeg skip links toe in templates.
  • Test met keyboard-only, schermlezer (NVDA/VoiceOver) en onze WCAG checker/validator.
  • Integreer linting/CI checks (axe-core, pa11y) in je build pipeline en gebruik onze plugin voor snelle feedback.

Tips voor designers en redacties

Designsystemen en focus

Ontwerp altijd een zichtbare focusstijl in je design library: kleur + offset + minimale dikte. Voorzie states voor :focus, :hover en :active.

Contrast en kleurgebruik

Zorg dat focus-indicatoren voldoende contrast hebben (controleer met onze checker). Gebruik niet alleen kleur om focus aan te geven; voeg vorm/vergroting of een rand toe.

Content- en redactierichtlijnen

Maak linktekst beschrijvend (geen ‘klik hier’), gebruik headings hiërarchie correct zodat keyboard- en screenreader-gebruikers snel kunnen navigeren. Templates moeten skip links en landmarks standaard bevatten.

Hoe test je dit?

Handmatige keyboard-check (stap-voor-stap)

  1. Verwijder muis, navigeer met Tab. Controleer dat alle links/knoppen bereiken en in logische volgorde tabben.
  2. Activeer elementen met Enter en Space (voor knoppen en custom controls).
  3. Open/Sluit modals: focus moet in modal komen en bij sluiten terug naar trigger.
  4. Gebruik Shift+Tab en controleer reverse volgorde.
  5. Controleer skip links: activeer en verifieer dat focus naar hoofdcontent gaat.

Quick screenreader-check

  1. Start NVDA (Windows) of VoiceOver (macOS).
  2. Navigeer via headings (H), links (K) en region landmarks; controleer of structuur logisch is.
  3. Bij dynamische updates: controleer of belangrijke content aankondigingen krijgt (aria-live of role=alert).

Automatische tests en CI

Gebruik axe-core, pa11y of onze WCAG checker API in je pipeline. Hieronder een minimal Puppeteer + axe snippet die je direct gebruikt en in CI draait:

const puppeteer=require('puppeteer');const AxeBuilder=require('@axe-core/puppeteer').default;(async()=>{const browser=await puppeteer.launch();const page=await browser.newPage();await page.goto('https://example.com');const results=await new AxeBuilder({page}).analyze();console.log(results.violations);await browser.close();})();

Test-assert voor modal focus (automatisch)

// Puppeteer snippet: na openen modal, check activeElement binnen modalconst active=await page.evaluate(()=>document.activeElement.id);if(active!=='closeModal'){throw new Error('Focus not set to modal close button');}

Gebruik onze tools

Run snel een scan met onze WCAG checker/validator op https://wcagtool.nl/checker, download onze browser-plugin voor live feedback tijdens development en voeg de checker-API toe aan je CI. Vragen? Gebruik het contactformulier op WCAGtool.nl — wij beantwoorden binnen 24 uur.

Praktische tip om meteen toe te passen: voeg deze kleine helperfunctie toe in je utilities om focus programmatic en zonder scroll te zetten:

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

Test nu je pagina met onze WCAG checker/validator, installeer de plugin voor realtime advies en stuur vragen via het contactformulier — respons binnen 24 uur.