Keyboard-navigatie en focusbeheer: praktisch toepassen (WCAG)
Keyboard-toegankelijkheid en juist focusbeheer zijn onderdelen waar implementaties het vaak laten afweten: niet-standaard tabvolgorde, ontbrekende focusstijlen, modals die focus ‘vangen’ zonder terug te geven en custom controls die niet werken met Enter/Space. Dat leidt tot onbruikbaarheid voor keyboard-only gebruikers en problemen in audits.
Wij lossen dit praktisch op met concrete code-snippets, testbare patronen en snelle checklists die developers, frontend engineers, UX-designers en redacties direct kunnen toepassen. Test je site meteen met onze WCAG checker en download de plugin op wcagtool.nl/plugin; bij vragen reageert ons team binnen 24 uur via wcagtool.nl/contact.
Het probleem in de praktijk
Veelvoorkomende fouten
- Tabindex misbruik: tabindex=”0″ en positieve tabindex-waarden die de natuurlijke volgorde breken.
- Ontbrekende focus-visible stijlen (outline: none; zonder alternatief).
- Custom controls (divs/buttons) die geen keyboard handlers of ARIA hebben.
- Modal dialogs die focus niet trapten of niet teruggeven na sluiten.
- SPA-navigatie zonder focusmanagement bij route-wissels.
Waarom dit direct impact heeft
WCAG 2.1 vereist keyboardbediening (2.1.1) en zichtbare focus (2.4.7). Fouten maken content onbereikbaar, verhogen assistentietools-frustratie en leiden tot non-conformiteit in audits.
Zo los je dit op in code
1. Gebruik semantische HTML en standaard elementen
Start altijd met <button>, <a href>, <input> in plaats van generieke <div>/<span>. Browsers verzorgen keyboard toegankelijkheid standaard.
<button type="button" class="primary">Opslaan</button><br><a href="/contact" class="link">Contact</a>
2. Focus zichtbaar maken zonder accessibility te breken
Voorkom outline: none; zonder alternatief. Geef duidelijke focusstijl die designvriendelijk is:
.focus-ring:focus { outline: 3px solid #005fcc; outline-offset: 2px; box-shadow: 0 0 0 3px rgba(0,95,204,0.15); }
3. Vermijd positieve tabindex-waarden
Gebruik nooit positieve tabindex tenzij je exact begrijpt waarom. Gebruik tabindex="0" om een element focusable te maken, en tabindex="-1" om programmatische focus toe te kennen (bv. bij modals).
<div role="button" tabindex="0" class="custom-btn">Klik</div> <!-- liever: <button> -->
4. Maak custom controls keyboard- en ARIA-vriendelijk
Voor een custom toggle:
<div role="switch" aria-checked="false" tabindex="0" id="toggle1"><span class="label">Notitie aan</span></div><br><script>const t=document.getElementById('toggle1');t.addEventListener('click',toggle);t.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();toggle();}});function toggle(){const s=t.getAttribute('aria-checked')==='true';t.setAttribute('aria-checked',String(!s));}</script>
5. Modal dialog: trap focus en geef focus terug
Voor modals is goed focusmanagement cruciaal: bij openen focus naar eerste focusable, trap tab, bij sluiten focus terug naar trigger.
<button id="openModal">Open</button><div id="modal" role="dialog" aria-modal="true" aria-hidden="true" tabindex="-1"><button id="closeModal">Sluit</button></div><script>const open=document.getElementById('openModal');const modal=document.getElementById('modal');const close=document.getElementById('closeModal');let lastFocused=null;open.addEventListener('click',()=>{lastFocused=document.activeElement;modal.setAttribute('aria-hidden','false');modal.focus();trapFocus(modal);} );close.addEventListener('click',closeModal);function closeModal(){modal.setAttribute('aria-hidden','true');lastFocused?.focus();}function trapFocus(container){const focusables=container.querySelectorAll('a[href],button,textarea,input,select,[tabindex]:not([tabindex="-1"])');let i=0;container.addEventListener('keydown',e=>{if(e.key==='Tab'){if(e.shiftKey&&document.activeElement===focusables[0]){e.preventDefault();focusables[focusables.length-1].focus();}else if(!e.shiftKey&&document.activeElement===focusables[focusables.length-1]){e.preventDefault();focusables[0].focus();}}});}
6. SPA-routing: focus naar page-title na route change
Zorg dat bij route-wissel focus naar een skip-target of page title gaat zodat screenreaders en keyboard gebruikers direct context krijgen.
// voorbeeld met vanilla JS na route change document.getElementById('page-title').setAttribute('tabindex','-1');document.getElementById('page-title').focus();
7. Gebruik ARIA correct — niet als vervanger van semantiek
ARIA is een hulpmiddel, geen vervanging van juiste HTML. Gebruik role alleen als element geen semantiek heeft.
Checklist voor developers
- Gebruik zoveel mogelijk semantische HTML (buttons, links, form controls).
- Geen positieve
tabindexwaarden;tabindex="0"of-1alleen wanneer nodig. - Alle interactieve elementen moeten keyboard-toegankelijk zijn (Enter/Space voor buttons/toggles).
- Focus-styles zichtbaar en duidelijk; geen
outline: none;zonder alternatief. - Modals trapten focus en geven focus terug; aria-modal, role=”dialog”.
- Bij route-wissels focus naar hoofd- of skip-link.
- Screenreader labels: gebruik
aria-labelof visuele labels gekoppeld metfor/id. - Automatische focus verplaatsen alleen wanneer gebruikers actie verwachten; bij content-updates geef context (role=”status” of aria-live).
Tips voor designers en redacties
Design: focus zichtbaar in design system
Maak focus-elementen onderdeel van component library: kleur, offset en shadow vastleggen. Test in greyscale en met hoge contrastinstellingen.
Content: link- en knop-tekst duidelijk en contextueel
Gebruik beschrijvende linkteksten (geen “klik hier”). Voeg aanvullende context toe met aria-describedby wanneer nodig.
Component guidelines voor redacties
- Gebruik semantische elementen uit CMS (bijv.
buttonin Rich Text toolbar). - Vermijd inline JavaScript die keyboard handlers ontbreken.
- Controleer geïmporteerde third-party widgets op keyboard-ondersteuning en focusgedrag.
Hoe test je dit?
Handmatige keyboard-tests
- Schakel muis uit of leg hand op toetsenbord; navigeer met Tab / Shift+Tab door de pagina. Alles wat klikbaar is moet focus kunnen krijgen.
- Activeer interactieve items met Enter en Space. Controleer dat custom controls werken.
- Open modals en navigeer binnen; Tab mag niet buiten de modal komen. Sluit modal en controleer dat focus terugkeert naar trigger.
- Bij route-change: navigeer via interne links en toets of focus op page-title of skip-link landt.
Automated checks en tools
Gebruik onze WCAG checker op wcagtool.nl/checker voor een eerste scan en concrete foutmeldingen gericht op keyboard en focus. Combineer met axe, Lighthouse en screenreader-tests (NVDA/VoiceOver).
Screenreader-tests
Controleer labels en rol-aanduiding met NVDA (Windows) of VoiceOver (macOS). Lees of de focusvolgorde en labels logisch zijn.
Regressietest in CI
Voeg tests toe: Cypress + axe-core voor geautomatiseerde regressie. Voor focus-specifieke tests kun je tab-simulatie gebruiken:
// Cypress voorbeeld cy.visit('/page');cy.realPress('Tab');cy.focused().should('have.attr','id','skip-link');
Praktische mini-how-to’s
Skip link implementatie
Een eenvoudige skip link verbetert keyboard UX direct:
<a href="#main" class="skip-link">Sla navigatie over</a><!-- CSS kiesbaar -->
Focus-ring alleen zichtbaar voor keyboardgebruikers
Gebruik een class toggled door JS op sleutelinteractie, of moderne :focus-visible:
/* voorkeur: focus-visible */:focus-visible{outline:3px solid #005fcc;outline-offset:2px;}/* fallback (als je oude browsers wilt): voeg .using-keyboard op body toe via JS */
Detecteer keyboardgebruik voor fallback focus-styles
window.addEventListener('keydown',function onFirstKey(e){if(e.key==='Tab'){document.body.classList.add('using-keyboard');window.removeEventListener('keydown',onFirstKey);}});
ARIA-live voor dynamische updates
Gebruik aria-live=”polite” voor statusmeldingen en role=”status” voor minimale interruptie:
<div aria-live="polite" id="status"></div> // update via JS document.getElementById('status').textContent='Opgeslagen';
Concrete code-snippets als copy-paste
Accessible custom dropdown
<div class="dropdown" role="combobox" aria-expanded="false" aria-haspopup="listbox" tabindex="0" id="dd1"><span class="selected">Kies</span><ul role="listbox" tabindex="-1"><li role="option" tabindex="-1">Optie 1</li><li role="option" tabindex="-1">Optie 2</li></ul></div><script>const dd=document.getElementById('dd1');const list=dd.querySelector('[role=\"listbox\"]');dd.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();const expanded=dd.getAttribute('aria-expanded')==='true';dd.setAttribute('aria-expanded',String(!expanded));if(!expanded){list.querySelector('[role=\"option\"]').focus();}}});list.addEventListener('keydown',e=>{if(e.key==='ArrowDown'){e.preventDefault();let next=document.activeElement.nextElementSibling||list.firstElementChild;next.focus();}if(e.key==='ArrowUp'){e.preventDefault();let prev=document.activeElement.previousElementSibling||list.lastElementChild;prev.focus();}});
Checklist voor QA / content editors
- Voer keyboard-only walkthrough uit, noteer onbereikbare items.
- Controleer dat alle CTA’s duidelijke tekst hebben en niet “klik hier” heten.
- Test modals, dropdowns en third-party widgets op keyboard-gedrag.
- Run onze WCAG checker: wcagtool.nl/checker voor directe foutrapporten.
Hoe wij kunnen helpen
Gebruik onze WCAG checker op wcagtool.nl/checker voor een gratis eerste audit. Download de plugin op wcagtool.nl/plugin om snel tijdens development issues te vinden. Voor gerichte hulp of reviews: stuur je vraag via wcagtool.nl/contact — we reageren binnen 24 uur.
Test je site nu: bezoek wcagtool.nl/checker en voer een snelle scan uit om focus- en keyboardproblemen te vinden en concrete fixes te krijgen.
Laatste praktische tip
Voeg in je build pipeline een accessibility gate toe: draai axe-core in CI en faal de build bij regressies. Snelle codevoorbeelden waarmee je direct start:
// Node script met axe-core en Puppeteer const { AxePuppeteer } = require('axe-puppeteer');const puppeteer = require('puppeteer');(async ()=>{const browser=await puppeteer.launch();const page=await browser.newPage();await page.goto('https://jouwsite.example');const results=await new AxePuppeteer(page).analyze();console.log(results.violations);await browser.close();})();
En vergeet niet: test je site direct met onze tool op wcagtool.nl/checker, download de dev-plugin op wcagtool.nl/plugin en bij vragen: wcagtool.nl/contact (antwoord binnen 24 uur).