PDF-toegankelijkheid: tools en workflow om PDF’s WCAG-proof te maken

Praktische implementatie: Keyboard en focus management – wcagtool.nl

Keyboard en focus management: praktisch toepassen

Keyboard-toegankelijkheid en juist focusbeheer falen vaak in productie omdat componenten visueel kloppen maar niet functioneel via toetsen bedienbaar zijn. Ontwikkelaars gebruiken niet-semantische elementen, designers verbergen focus-styles en modals breken focus-traps: resultaat is onbruikbare interfaces voor toetsenbordgebruikers.

Wij lossen dit op met concrete patterns, getestbare code-snippets en checklists die je direct kunt implementeren. Test je site meteen met onze WCAG checker of download onze plugin voor snelle feedback; vragen? Gebruik het contactformulier — we reageren binnen 24 uur.

Het probleem in de praktijk

Veelvoorkomende fouten

  • Interactieve elementen zijn geen echte buttons/links (div/span zonder role/keyboard-support).
  • Geen zichtbare focus (outline: none zonder alternatief).
  • Modals en dialogs breken focus flow, geen terugzetten van focus na sluiten.
  • Custom widgets missen ARIA-attributes en keyboard-handler voor Enter/Escape/Arrow.
  • Tabindex misbruik: negatieve tabindex voor interactieve items of teveel tabindex=”1/2″.

Waarom dit impact heeft op WCAG

WCAG 2.1 vereist toetsenbord-toegankelijkheid (2.1.1), focus-visible (2.4.7) en navigatie (2.4.*). Fouten hierboven leiden direct tot niet voldoen aan die succescriteria.

Zo los je dit op in code

Basisregel: gebruik semantische HTML

Gebruik button, a en input waar mogelijk. Pas ARIA alleen toe om semantics aan te vullen, niet te vervangen.

<!-- Goede keuze: native button is keyboard-ready en toegankelijk --><br><button type="button" class="btn" id="saveBtn">Opslaan</button>

Skip-link: verplicht voor lange pagina’s

Voeg een skip-link toe die zichtbaar wordt bij focus zodat toetsenbordgebruikers snel naar content kunnen springen.

<a href="#main" class="skip-link">Sla naar hoofdinhoud over</a><br><!-- CSS --><br>.skip-link{position:absolute;left:-999px;top:auto;width:1px;height:1px;overflow:hidden}<br>.skip-link:focus{position:static;left:auto;width:auto;height:auto;clip:none;padding:8px;background:#000;color:#fff;z-index:1000}

Focus-styles: gebruik :focus-visible

Verwijder nooit outlines zonder alternatief. Gebruik :focus-visible om visuele rommel te beperken maar behoud zichtbare focus.

/* zichtbare focus */<br>:focus-visible{outline:3px solid #005fcc;outline-offset:2px;border-radius:2px}

Accessible modal / dialog pattern

Belangrijk: aria-modal, role=”dialog”, focus trap bij openen en restore focus bij sluiten.

<!-- HTML --><br><button id="openModal">Open dialog</button><br><div id="dialog" role="dialog" aria-modal="true" aria-hidden="true" tabindex="-1"><br>  <button id="closeModal">Sluiten</button><br>  <div>Inhoud van modal</div><br></div><br><!-- JavaScript: simpel focus trap & restore --><br>const openBtn = document.getElementById('openModal');<br>const dialog = document.getElementById('dialog');<br>const closeBtn = document.getElementById('closeModal');<br>let lastFocused = null;<br>function openDialog(){ lastFocused = document.activeElement; dialog.setAttribute('aria-hidden','false'); dialog.style.display='block'; dialog.focus(); document.addEventListener('keydown',trapTab); document.addEventListener('keydown',handleEsc);} <br>function closeDialog(){ dialog.setAttribute('aria-hidden','true'); dialog.style.display='none'; document.removeEventListener('keydown',trapTab); document.removeEventListener('keydown',handleEsc); if(lastFocused) lastFocused.focus(); } <br>function handleEsc(e){ if(e.key==='Escape'){ closeDialog(); }} <br>function trapTab(e){ if(e.key!=='Tab') return; const focusable = dialog.querySelectorAll('a[href], button, textarea, input, select, [tabindex]:not([tabindex=\"-1\"])'); const first = focusable[0]; const 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(); }} <br>openBtn.addEventListener('click',openDialog); closeBtn.addEventListener('click',closeDialog);

Custom interactive widgets: voorbeeld dropdown

Als je geen native select gebruikt, implementeer keyboard-bediening en ARIA attributes.

<!-- minimal accessible dropdown --><br><div class="dropdown" role="listbox" tabindex="0" aria-labelledby="label" aria-expanded="false"><br>  <div id="label">Kies optie</div><br>  <ul role="presentation" class="options" hidden><br>    <li role="option" tabindex="-1">Optie 1</li><br>    <li role="option" tabindex="-1">Optie 2</li><br>    <li role="option" tabindex="-1">Optie 3</li><br>  </ul><br></div><br><!-- JS: open/close en keyboard navigation --><br>const dd = document.querySelector('.dropdown'); const options = dd.querySelectorAll('[role=\"option\"]'); let idx = -1; dd.addEventListener('keydown',e => { if(e.key==='Enter' || e.key===' '){ const expanded = dd.getAttribute('aria-expanded')==='true'; dd.setAttribute('aria-expanded', String(!expanded)); dd.querySelector('.options').hidden = expanded; if(!expanded){ idx=0; options[idx].focus(); } e.preventDefault(); } else if(e.key==='ArrowDown'){ idx = Math.min(idx+1, options.length-1); options[idx].focus(); e.preventDefault(); } else if(e.key==='ArrowUp'){ idx = Math.max(idx-1,0); options[idx].focus(); e.preventDefault(); } else if(e.key==='Escape'){ dd.setAttribute('aria-expanded','false'); dd.querySelector('.options').hidden = true; dd.focus(); } });

Checklist voor developers

  • Gebruik semantische controls (button/a/input) waar mogelijk.
  • Zorg voor visible focus met :focus-visible; verwijder outline nooit zonder alternatief.
  • Voeg skip-link toe en test met keyboard-only.
  • Implementeer focus trap voor modals en restore focus na sluiten.
  • Voor custom components: voeg ARIA roles, aria-expanded, aria-hidden, aria-selected etc. toe en implementeer Enter/Space/Arrow/Escape handlers.
  • Gebruik tabindex=”0″ om niet-focusable elementen focusable te maken; gebruik tabindex=”-1″ alleen voor script-managed focus.
  • Test met screenreader + keyboard en met onze WCAG checker/validator (https://wcagtool.nl/validator).

Tips voor designers en redacties

Design-systeem regels

  • Documenteer focus-states in het design system (kleur, grootte, offset).
  • Vermijd hover-only interacties; zorg voor keyboard alternatieven.
  • Voor content-editors: geen inline elementen als knoppen; gebruik CMS-componenten die semantisch juiste markup uitgeven.

Redactionele richtlijnen

  • Zorg dat links tekst bevatten die betekenis geeft buiten context (geen “klik hier”).
  • Gebruik kopstructuur (h1-h6) logisch; dat verbetert keyboard-navigation en screenreader-ervaring.

Hoe test je dit?

Handmatige tests (snel en effectief)

  1. Schakel muis weg: navigeer alleen met Tab/Shift+Tab en toets Enter/Space. Alle interactieve items moeten bereikbaar en bedienbaar zijn.
  2. Controleer focus volgorde: logische, voorspelbare volgorde van elementen.
  3. Open modals, probeer Tab en Shift+Tab om te verifiëren dat focus niet buiten de modal gaat.
  4. Test met Escape: modals en overlays moeten sluiten en focus moet teruggaan naar de trigger.
  5. Controleer zichtbare focus op alle controles (ook custom components).

Automated checks en tools

Gebruik onze WCAG checker/validator (https://wcagtool.nl/validator) voor snelle scans. Daarnaast: axe-core, Lighthouse en WAVE voor aanvullende fouten. Integreer onze plugin (https://wcagtool.nl/plugin) in je workflow voor real-time feedback.

Screenreader tests

Test met NVDA/JAWS (Windows) en VoiceOver (macOS/iOS). Controleer dat labels worden voorgelezen en dat aria-states correct veranderen bij interactie.

Concrete mini-how-to’s

1) Maak een custom button toegankelijk

<!-- liever gebruik native button, maar zo maak je een div of span toegankelijk --><br><div role="button" tabindex="0" aria-pressed="false" id="myBtn">Schakel</div><br>const el = document.getElementById('myBtn'); el.addEventListener('click',toggle); el.addEventListener('keydown',e => { if(e.key==='Enter' || e.key===' '){ e.preventDefault(); toggle(); }}); function toggle(){ const pressed = el.getAttribute('aria-pressed')==='true'; el.setAttribute('aria-pressed', String(!pressed)); }

2) Restore focus na navigatie away / modal sluiten

// bij open: lastFocused = document.activeElement; // bij close: if(lastFocused) lastFocused.focus();

3) Vermijd tabindex-waarden >0

Gebruik nooit tabindex=”1″ of meer; dat breekt natuurlijke tab-volgorde. Zet focusable items op tabindex=”0″.

Belangrijke testscenario’s die je direct draait

  1. Keyboard-only: voltooi het primaire taakpad (zoek, selecteer, invullen, opslaan).
  2. Modal: open, navigeer, sluit met Escape en controleer terugzetten focus.
  3. Custom widgets: navigeer via Arrow-keys en activeer opties met Enter/Space.
  4. Focus zichtbaarheid: controleer contrast van focus-indicator en dat deze niet onzichtbaar is.

Calls-to-action

Test je pagina nu met onze WCAG checker: <a href=”https://wcagtool.nl/validator”>https://wcagtool.nl/validator</a>. Wil je realtime feedback in je browser? Download onze plugin: <a href=”https://wcagtool.nl/plugin”>https://wcagtool.nl/plugin</a>. Vragen over implementatie? Stuur ze via ons contactformulier (<a href=”https://wcagtool.nl/contact”>https://wcagtool.nl/contact</a>) — we antwoorden binnen 24 uur.

Laatste praktische tip

Voeg dit korte test-script toe aan je checklist en draai het na elke release: open alle modals, navigeer keyboard-only door alle interactieve componenten en verifieer dat focus nooit verdwijnt. Gebruik daarnaast direct onze validator (https://wcagtool.nl/validator) voor een snelle automatische controle.

Previous Post Next Post

Geef een reactie

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