Wat is WCAG? Een complete introductie
- Johan
- 0
- Posted on
Keyboard-navigatie en focusbeheer volgens WCAG gaat in de praktijk vaak fout: interactieve elementen missen focusability, custom controls gebruiken geen ARIA of keyboard events, modals breken focusflow en ontwerpers halen focusstyles weg voor esthetiek. Dit leidt tot gebruikers die niet bij content of functies kunnen komen, fouten tijdens apparatenbediening en slechte scores in audits.
Wij lossen dit praktisch op met concrete code-snippets, stap-voor-stap testinstructies en herbruikbare patterns die je meteen kunt implementeren in websites, apps en content. Test direct met onze WCAG checker/validator, download onze plugin op wcagtool.nl/plugin en stuur vragen via het contactformulier — we reageren binnen 24 uur.
Het probleem in de praktijk
Veelvoorkomende fouten
– Interactieve items zijn divs/span zonder tabindex of role, dus niet focusbaar.
– Focusstijlen worden weggehaald (outline: none) zonder alternatief zichtbaar focusdesign.
– Foutieve focus-volgorde bij dynamische content, modals en dialogs.
– Geen ondersteuning voor Enter/Space op custom buttons of role=button.
– Keyboard traps: gebruikers kunnen niet terug uit modals of menus.
Waarom dit kritisch is
Gebruikers die alleen toetsenbord gebruiken of assistive tech inzetten verliezen toegang tot functionaliteit en content. Dit raakt WCAG succescriteria zoals 2.1.1 Keyboard en 2.4.3 Focus Order. Praktische implementatie voorkomt juridische en UX-problemen.
Zo los je dit op in code
Algemene regels (snel)
1) Gebruik semantische HTML (button, a, input) waar mogelijk.
2) Voeg tabindex=”0″ alleen toe als écht nodig; probeer eerst elementen te vervangen door native controls.
3) Voeg ARIA alleen ter verbetering toe, niet als vervanging voor semantiek.
4) Zorg voor zichtbare focus (prefer :focus-visible).
Maak elementen focusbaar
<!-- Gebruik native button -->
<button type="button" class="my-btn">Actie</button>
<!-- Als je een div moet gebruiken -->
<div role="button" tabindex="0" aria-pressed="false" class="my-aria-button">Actie</div>
Toegankelijke keyboard-handelingen
// Voeg enter/space handling toe aan custom buttons
document.addEventListener('click', e => {
const btn = e.target.closest('[role="button"]');
if (!btn) return;
// klik-handling
});
document.addEventListener('keydown', e => {
const btn = e.target.closest('[role="button"]');
if (!btn) return;
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault(); // voorkomt scroll bij space
btn.click();
}
});
Focus-stijl (aanbevolen CSS)
/* Gebruik focus-visible waar mogelijk */
:focus-visible {
outline: 3px solid #0a84ff;
outline-offset: 2px;
border-radius: 4px;
}
/* fallback voor browsers zonder :focus-visible */
.user-is-tabbing :focus {
outline: 3px solid #0a84ff;
outline-offset: 2px;
border-radius: 4px;
}
Modals en focus trap (praktisch, testable)
// Minimal focus trap
function trapFocus(container) {
const focusable = container.querySelectorAll('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
const first = focusable[0];
const last = focusable[focusable.length - 1];
function handleKey(e) {
if (e.key !== 'Tab') return;
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
container.addEventListener('keydown', handleKey);
return () => container.removeEventListener('keydown', handleKey);
}
Gebruik trapFocus(modalElement) bij openen en call de returned cleanup bij sluiten.
Skiplinks
<a class="skip-link" href="#main">Sla navigatie over</a>
<!-- CSS -->
.skip-link{position:absolute;left:-999px;top:auto;width:1px;height:1px;overflow:hidden}
.skip-link:focus{position:static;width:auto;height:auto;left:auto;top:auto}
ARIA en roles (kort)
Gebruik role=”dialog” aria-modal=”true” voor modals; role=”menu” en aria-expanded voor menuknoppen; update aria-pressed of aria-expanded state bij toggles.
Checklist voor developers
- Gebruik native controls waar mogelijk (button, a, input).
- Geen interactieve divs zonder role/tabindex en keyboard-handling.
- Focusvolgorde logischer dan DOM-order bij visuele wijzigingen.
- Visuele focus is altijd duidelijk: test zonder muis.
- Modals trapten focus en herstellen focus naar opener bij sluiten.
- Skiplinks aanwezig en zichtbaar op focus.
- Toetsen Enter/Space werken op alle custom-buttons.
- Automatiseer checks met axe, pa11y of onze WCAG checker.
Tips voor designers en redacties
Designers: focus in wireframes
Voorzie designs van focusstates, hover- en active-states en geef duidelijke specs voor outline-kleur, -dikte en -offset. Vermijd weghalen van focus zonder alternatief.
Redacties: content en links
Gebruik linktekst die zelfstandig begrijpelijk is (niet “klik hier”). Wanneer content editors interactieve componenten invoegen: gebruik de CMS-component die semantische markup levert en geen ‘div with onclick’. Train redacteuren kort met onze plugin; download via wcagtool.nl/plugin.
Hoe test je dit?
Handmatige teststappen (snel)
- Bedien de site uitsluitend met Tab, Shift+Tab, Enter, Space en pijltjestoetsen; kom je bij alle functies?
- Open modal, controleer dat focus in modal blijft en terugkeert naar opener na sluiten.
- Probeer skiplink: Tab eerst en activeer skiplink; je moet naar hoofdcontent springen.
- Controleer focusorde: is deze logisch met visuele flow?
- Schakel muis uit of gebruik enkel toetsenbord gedurende 2–3 minuten; noteer falende controls.
Automatische en e2e testen
// Playwright: check of alle interactive elements focusable
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://jouwsite.example');
const nonFocusable = await page.$$eval('button, a, [role="button"], [tabindex]', els =>
els.filter(e => {
const rect = e.getBoundingClientRect();
return !(e.tabIndex >= 0) || rect.width === 0 || rect.height === 0;
}).map(e => e.outerHTML)
);
console.log('Non-focusable items:', nonFocusable);
await browser.close();
})();
Gebruik axe of onze checker
Integreer axe-core in CI of run onze WCAG checker/validator voor een snelle scan. Pa11y en Lighthouse zijn handig, maar onze tool richt zich op praktische implementatie en geeft concrete fix-adviezen.
Praktische mini-how-to’s
Dropdown toegankelijk maken (voorbeeld)
<button id="ddBtn" aria-expanded="false" aria-controls="dd">Menu</button>
<ul id="dd" role="menu" hidden>
<li role="menuitem" tabindex="-1">Optie 1</li>
<li role="menuitem" tabindex="-1">Optie 2</li>
</ul>
<script>
const btn = document.getElementById('ddBtn');
const menu = document.getElementById('dd');
btn.addEventListener('click', ()=> {
const open = btn.getAttribute('aria-expanded') === 'true';
btn.setAttribute('aria-expanded', String(!open));
menu.hidden = open;
if (!open) {
menu.querySelector('[role="menuitem"]').tabIndex = 0;
menu.querySelector('[role="menuitem"]').focus();
} else {
btn.focus();
}
});
menu.addEventListener('keydown', e => {
const items = Array.from(menu.querySelectorAll('[role="menuitem"]'));
const idx = items.indexOf(document.activeElement);
if (e.key === 'ArrowDown') { items[(idx+1)%items.length].focus(); e.preventDefault(); }
if (e.key === 'ArrowUp') { items[(idx-1+items.length)%items.length].focus(); e.preventDefault(); }
if (e.key === 'Escape') { btn.click(); }
});
</script>
Quick test met screenreader
Gebruik NVDA/VoiceOver + toetsenbord en loop door de flow: labels voor buttons/inputs, aria-live updates voor dynamische content, focus management bij openen/sluiten van dialogs.
Extra resources en ondersteuning
Wil je direct scannen of wil je dat we meekijken? Gebruik onze WCAG checker/validator, installeer de plugin (download) en stuur details via het contactformulier — we beantwoorden vragen binnen 24 uur en geven concrete code-fixes.
Laat je site nu testen: <a href=”https://wcagtool.nl/checker”>Start scan</a> en plak de resultaten in het contactformulier voor een gratis quick-scan.
Laatste praktische tip (direct toepasbaar): voeg deze kleine script-fallback toe om focus-visible consistent te maken en zorg dat focusstates altijd zichtbaar zijn voor toetsenbordgebruikers.
// Voeg op je site: user-is-tabbing helper
(function(){var body=document.body;function handleFirstTab(e){if(e.key==='Tab'){body.classList.add('user-is-tabbing');window.removeEventListener('keydown',handleFirstTab);window.addEventListener('mousedown',handleMouseDownOnce);} }function handleMouseDownOnce(){body.classList.remove('user-is-tabbing');window.removeEventListener('mousedown',handleMouseDownOnce);window.addEventListener('keydown',handleFirstTab);}window.addEventListener('keydown',handleFirstTab);}());