Automatische accessibility tools: wat ze wel en niet vinden





Keyboardtoegankelijkheid & focusmanagement — praktisch toepassen | WCAGTool


Keyboardtoegankelijkheid & focusmanagement: praktische implementatie

Keyboardtoegankelijkheid en focusmanagement falen in vrijwel elk project: skip-links ontbreken, modals breken tabvolgorde, custom controls reageren niet op Enter/Space of pijltjestoetsen en tabindex wordt verkeerd gebruikt. Dit leidt tot ontoegankelijke functionaliteit voor gebruikers die niet of beperkt met een muis werken.

Wij lossen dit op met concrete patterns, testbare code-snippets en direct toepasbare checklists. Gebruik onze voorbeelden, test je site direct met de WCAG checker en download onze plugin voor snelle scans en verbeteracties.

Het probleem in de praktijk

In de praktijk zie je een aantal terugkerende fouten:

  • Skip-links ontbreken of zijn niet zichtbaar bij focus.
  • Tab-volgorde is niet logisch door gebruik van tabindex>0 of visueel verplaatste elementen.
  • Modals en dialogs breken de focusflow; focus wordt niet teruggezet of niet vastgehouden.
  • Custom controls (dropdowns, tabs, sliders) implementeren geen keyboard-events of ARIA patterns.

Waarom dit vaak misgaat

Ontwikkelaars kopiëren visuele componenten zonder ARIA/keyboard logica, designers geven geen keyboard-staten mee en redacties publiceren content met interactieve elementen (iframes, widgets) zonder toegankelijke fallback. Teams missen vaak een standaard checklist en automatische tooling.

Zo los je dit op in code

1) Skip-link: basis en zichtbaar bij focus

Voeg een skip-link toe die zichtbaar wordt bij focus zodat keyboardgebruikers direct naar de hoofdcontent springen.

<a href="#main-content" class="skip-link">Naar hoofdinhoud</a>
<!-- CSS -->
.skip-link {
  position: absolute;
  left: -999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.skip-link:focus,
.skip-link:active {
  position: fixed;
  left: 1rem;
  top: 1rem;
  width: auto;
  height: auto;
  padding: .5rem .75rem;
  background:#005; color:#fff; z-index:1000;
}
<!-- Plaats de target -->
<main id="main-content">...</main>

2) Gebruik tabindex correct: nooit tabindex>0

tabindex>0 verandert volgorde en veroorzaakt onvoorspelbare tabopvolging. Gebruik tabindex=”0″ om niet-focusbare elementen focusbaar te maken, en tabindex=”-1″ om focus programmatically te zetten (bijv. after opening a modal).

<!-- Maak een niet-interactief element programmatically focusbaar -->
<div id="message" tabindex="-1">Modal geopend</div>
<!-- Na openen in JS -->
document.getElementById('message').focus();

3) Toegankelijke modal / dialog pattern (HTML + JS)

Volg role=”dialog”, aria-modal=”true”, focus trap en focus restore. Hieronder een compact, testbaar pattern.

<!-- HTML -->
<button id="openModal">Open modal</button>
<div id="modal" class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle" hidden>
  <h2 id="modalTitle">Dialog titel</h2>
  <button id="closeModal">Sluit</button>
  <!-- content -->
</div>

<!-- CSS (eenvoudig) -->
.modal[hidden] { display:none; }
.modal { position:fixed; inset:0; display:flex; align-items:center; justify-content:center; background:rgba(0,0,0,.5); }

<!-- JS -->
(function(){
  const openBtn = document.getElementById('openModal');
  const modal = document.getElementById('modal');
  const closeBtn = document.getElementById('closeModal');
  let lastFocus = null;

  function trapFocus(e){
    const focusable = modal.querySelectorAll('a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])');
    const first = focusable[0];
    const last = focusable[focusable.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();
      }
    }
    if (e.key === 'Escape') { closeModal(); }
  }

  function openModal(){
    lastFocus = document.activeElement;
    modal.removeAttribute('hidden');
    document.body.style.overflow = 'hidden';
    modal.addEventListener('keydown', trapFocus);
    const focusable = modal.querySelectorAll('a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])');
    (focusable[0] || modal).focus();
    modal.setAttribute('aria-hidden','false');
  }

  function closeModal(){
    modal.setAttribute('hidden','');
    modal.removeEventListener('keydown', trapFocus);
    document.body.style.overflow = '';
    modal.setAttribute('aria-hidden','true');
    if (lastFocus) lastFocus.focus();
  }

  openBtn.addEventListener('click', openModal);
  closeBtn.addEventListener('click', closeModal);
})();

4) Custom select / dropdown: roving tabindex of native select

Gebruik bij voorkeur native <select>. Voor custom dropdowns implementeer ARIA en keyboard gedrag: Enter/Space open, Esc sluit, pijlen navigeren.

<!-- Minimal roving tabindex pattern -->
<div role="listbox" aria-activedescendant="option-1" tabindex="0">
  <div id="option-1" role="option" tabindex="-1">Optie 1</div>
  <div id="option-2" role="option" tabindex="-1">Optie 2</div>
</div>

<!-- JS: focus management for arrows -->
const listbox = document.querySelector('[role="listbox"]');
const options = Array.from(listbox.querySelectorAll('[role="option"]'));
let index = 0;
listbox.addEventListener('keydown', e => {
  if (e.key === 'ArrowDown') { index = Math.min(index+1, options.length-1); options[index].focus(); e.preventDefault(); }
  if (e.key === 'ArrowUp')   { index = Math.max(index-1, 0); options[index].focus(); e.preventDefault(); }
  if (e.key === 'Enter')     { options[index].click(); e.preventDefault(); }
});

5) Buttons vs links: semantiek en keyboard

Gebruik <button> voor acties en <a> voor navigatie. Maak geen clickable <div> zonder role en keyboard handlers.

<!-- FOUT: div met click only -->
<div class="btn" onclick="doAction()">Actie</div>

<!-- GOED: semantic button -->
<button type="button" onclick="doAction()">Actie</button>

Checklist voor developers

  • Voorkom tabindex>0; gebruik tabindex=”0″ of tabindex=”-1″ waar nodig.
  • Voeg skip-link toe en test zichtbaarheid bij focus.
  • Gebruik native controls tenzij een accessible custom control nodig is.
  • Voor modals: role=”dialog” aria-modal=”true”, trap focus, restore focus.
  • Controleer alle interactieve elementen op Enter/Space en pijltjestoets-ondersteuning.
  • Zorg dat visuele focusstaat (focus-visible) consistent is in CSS.
  • Gebruik aria-live waar inhoud dynamisch verschijnt (alerts, validation).
  • Automatiseer checks met onze WCAG checker en installeer onze plugin voor CI-feedback.

Tips voor designers en redacties

Ontwerp states en taal

Includeer focus- en hover-states in designs en lever CSS-voorbeelden aan. Geef aan wanneer keyboard-only alternatieven nodig zijn.

Content: vermijd onnodige tabindex en focusable wrapper elementen

Redacties: vink interactieve embeds na; als een widget niet keyboardtoegankelijk is, verwijder hem of voeg de accessible variant toe. Gebruik duidelijke linkteksten (geen “klik hier”).

Voorbeeld CSS voor focus-visible

:focus { outline: 2px solid #005; outline-offset: 2px; }
:focus:not(:focus-visible) { outline: none; } /* voorkomt onnodige outlines bij muis */

Hoe test je dit?

Handmatige toetsen (snel en betrouwbaar)

  1. Tab door de pagina: elk interactief element moet focus krijgen in logische volgorde.
  2. Shift+Tab: reverse volgorde werkt en geeft geen focus-lock.
  3. Enter/Space: activeert knoppen en links consistent.
  4. Pijltjestoetsen: navigeer tussen opties in composite widgets (tabs, listbox, roving tabindex).
  5. Esc: sluit modals en dropdowns; focus wordt teruggezet naar de opener.
  6. Gebruik alleen toetsen: voer volledige taken uit (formulier invullen, navigeren, items selecteren).

Automated checks en onze tooling

Start met een scan via onze WCAG checker op wcagtool.nl. Onze validator vindt veelvoorkomende fouten (tabindex>0, ontbrekende ARIA, contrastproblemen). Voor development workflow: download onze plugin op wcagtool.nl/plugin en voeg scans toe aan CI.

Test-cases die je moet opnemen

  • Open en sluit modal: focus trap en restore.
  • Form validation via keyboard: focus naar de foutmelding (aria-describedby/aria-invalid).
  • Custom widget keyboard-navigatie: pijltoetsen en Enter/Space werken.
  • Skip-link: zichtbaar en werkt.

Concrete mini-how-to’s

Focus naar een foutmelding na submit (HTML + JS)

<form id="signup">
  <input id="email" name="email">
  <div id="error" tabindex="-1" aria-live="assertive"></div>
  <button type="submit">Verstuur</button>
</form>

<script>
document.getElementById('signup').addEventListener('submit', function(e){
  e.preventDefault();
  const email = document.getElementById('email');
  const error = document.getElementById('error');
  if (!email.value.includes('@')) {
    error.textContent = 'Voer een geldig e-mailadres in';
    error.focus(); // tabindex="-1" maakt programma-focus mogelijk
    email.setAttribute('aria-invalid','true');
  } else {
    // submit
  }
});
</script>

Quick audit script: check for tabindex>0

const bad = Array.from(document.querySelectorAll('[tabindex]')).filter(el => Number(el.getAttribute('tabindex')) > 0);
console.log('tabindex>0 gevonden:', bad); // run in devtools

Werk direct met onze tools

Test je site nu met onze gratis WCAG checker op wcagtool.nl. Voor integratie in je workflow: download onze plugin via wcagtool.nl/plugin. Vragen? Vul ons contactformulier in op wcagtool.nl/contact — we reageren binnen 24 uur.

Praktische tip die je direct toepast: draai de volgende checklist in devtools en fix items in één sprint — daarna voer een scan met onze checker uit en koppel de plugin aan je CI voor regressie-checks.

Wil je hulp bij implementatie? Gebruik onze WCAG checker op wcagtool.nl, download de plugin op wcagtool.nl/plugin en stuur vragen via wcagtool.nl/contact. Wij reageren binnen 24 uur.

Laatste praktische codecheck (paste in console)

// Zoek snel focusable elementen zonder visible focus style (eenvoudig heuristic)
const foc = Array.from(document.querySelectorAll('a, button, input, textarea, select, [tabindex]'));
const noOutline = foc.filter(el => {
  const s = window.getComputedStyle(el);
  return s.outlineStyle === 'none' || s.outline === '0px none rgb(0, 0, 0)';
});
console.log('Elementen zonder outline-staat (controleer CSS):', noOutline);



Previous Post Next Post

Geef een reactie

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