Hoe test je toetsenbordnavigatie en focusbeheer

Praktische implementatie: Keyboard-navigatie en focusbeheer volgens WCAG | WCAGTool

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 tabindex waarden; tabindex="0" of -1 alleen 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-label of visuele labels gekoppeld met for/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. button in 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

  1. Schakel muis uit of leg hand op toetsenbord; navigeer met Tab / Shift+Tab door de pagina. Alles wat klikbaar is moet focus kunnen krijgen.
  2. Activeer interactieve items met Enter en Space. Controleer dat custom controls werken.
  3. Open modals en navigeer binnen; Tab mag niet buiten de modal komen. Sluit modal en controleer dat focus terugkeert naar trigger.
  4. 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).

Previous Post Next Post

Geef een reactie

Je e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *