Hoe maak je een WCAG-conform toegankelijkheidsverklaring

Toegankelijkheidsfouten: keyboard en focusbeheer praktisch opgelost

Keyboard-toegankelijkheid en focusbeheer zijn twee van de meest voorkomende implementatiefouten bij WCAG. Ontbrekende skip-links, onzichtbare focus-stijlen, verkeerde focusvolgorde en gebrekkige focus-management bij modals of dropdowns maken een site onbruikbaar voor keyboard- en assistive-technologiegebruikers.

Wij brengen dit terug naar concrete, testbare stappen: werkende codevoorbeelden, CSS- en JS-snippets en checklists die je direct in je project kunt plakken. Test vervolgens met onze WCAG checker/validator, download onze plugin en vraag ons via het contactformulier—vragen beantwoorden we binnen 24 uur.

Het probleem in de praktijk

Veelgemaakte fouten

  • Geen skip-link of onzichtbaar gemaakt door CSS
  • Focus outline weggehaald zonder alternatief
  • Focusverlies bij sluiten van modals of navigeren
  • Non-interactieve elementen met role=”button” zonder keyboard-events
  • Trap in focus bij dialogs/menus (focus trapping ontbreekt of faalt)

Voorbeeldsituaties

Forms met custom-controls die niet tabbable zijn; dropdowns die muis-only werken; focus die springt naar het einde van de pagina na AJAX-submit. Dit zijn allemaal direct reproduceerbaar en oplossen met de voorbeelden hieronder.

Zo los je dit op in code

1. Implementatie: Skip-link die altijd zichtbaar wordt bij focus

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

2. Gebruik toegankelijke focus-styles: behoud outline, verbeter visueel

/* Gebruik :focus-visible voor moderne browsers en fallback */<br>a:focus, button:focus, input:focus {outline: 3px solid #005fcc;outline-offset: 2px;} <br>:focus:not(:focus-visible) {outline: none;} <br>/* Zorg dat je niet volledig weglaat */

3. Rollen en keyboard events voor custom controls

<div role="button" tabindex="0" aria-pressed="false" id="customBtn">Toggle</div><br><script>const btn = document.getElementById('customBtn'); btn.addEventListener('click', toggle); btn.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggle(); } }); function toggle(){ const pressed = btn.getAttribute('aria-pressed') === 'true'; btn.setAttribute('aria-pressed', String(!pressed)); } </script>

4. Focus management bij modals (open, trap, close)

<!-- Minimal modal structure --><br><button id="openModal">Open modal</button><br><div id="modal" role="dialog" aria-modal="true" aria-hidden="true"><br>  <button id="closeModal">Close</button><br>  <a href="#">Link in modal</a><br></div><br><script>const open = document.getElementById('openModal'); const modal = document.getElementById('modal'); const close = document.getElementById('closeModal'); let lastFocused; const focusableSelector = 'a[href], button, textarea, input, select, [tabindex]:not([tabindex="-1"])'; open.addEventListener('click', () => { lastFocused = document.activeElement; modal.setAttribute('aria-hidden','false'); modal.style.display = 'block'; const focusable = modal.querySelectorAll(focusableSelector); focusable[0].focus(); document.body.style.overflow = 'hidden'; }); close.addEventListener('click', () => { modal.setAttribute('aria-hidden','true'); modal.style.display = 'none'; document.body.style.overflow = ''; if (lastFocused) lastFocused.focus(); }); document.addEventListener('keydown', (e) => { if (modal.getAttribute('aria-hidden') === 'false') { if (e.key === 'Tab') { const focusable = Array.from(modal.querySelectorAll(focusableSelector)); 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(); } } if (e.key === 'Escape') { close.click(); } } }); </script>

5. Schermlezers: aria-hidden correct gebruiken en inert toepassen

// Wanneer modal open is zet backdrop content inert of aria-hidden<br>document.getElementById('page').setAttribute('aria-hidden', 'true');<br>// Of gebruik inert-polyfill: element.inert = true; (verspreid in JS-builds) 

Checklist voor developers

  • Is alles wat interactief is tabbable of reachable via keyboard?
  • Zijn focus-indicatoren zichtbaar en meetbaar (contrast, grootte)?
  • Wordt focus logisch verplaatst bij overlays, dialogs en route-wijzigingen?
  • Werken custom controls met Enter en Spatie én hebben ze correcte ARIA-attributes?
  • Is aria-hidden niet gebruikt om elementen onbereikbaar te maken voor keyboard zonder alternatief?
  • Automatische focus-traps getest met Shift+Tab en Tab en met screenreader enabled?
  • Run onze WCAG checker/validator en axe-core tijdens CI (voorbeeld in testsectie).

Tips voor designers en redacties

Ontwerpregels

  • Maak focusvisueel onderdeel van het design-systeem (kleur, dikte, offset)
  • Vermijd hover-only interacties; ontwerp voor keyboard-first
  • Gebruik duidelijke visuele hiërarchie zodat focusvolgorde voorspelbaar is

Content-richtlijnen

  • Form labels altijd zichtbaar en gekoppeld met for/id of aria-labelledby
  • Interne links focussen content, niet alleen visuele effecten
  • Gebruik korte, duidelijke linkteksten; “Lees meer” alleen met context

Hoe test je dit?

Handmatig: keyboard-only testen

  1. Schakel muis uit (fysiek of negeer) en navigeer alleen met Tab, Shift+Tab, Enter, Space en pijltjestoetsen.
  2. Controleer of elke focusbare control zichtbare focus krijgt.
  3. Open en sluit modal/dialog en controleer terugzetten van focus.
  4. Test dropdowns en custom widgets met alleen keyboard.

Automatisch: axe-core en CI

// Voorbeeld in Node + jest + jest-puppeteer gebruikt met axe-core<br>const AxeBuilder = require('@axe-core/playwright').default; test('pagina is keyboard-accessible', async () => { await page.goto('https://jouw-site.test'); const results = await new AxeBuilder({page}).analyze(); expect(results.violations.length).toBe(0); });

Run daarnaast onze WCAG checker/validator en installeer de plugin (downloaden via /plugin) voor snel inzicht tijdens development.

Automated e2e test snippet (Cypress) voor modal focus)

cy.visit('/'); cy.get('#openModal').click(); cy.get('#modal').should('be.visible'); cy.focused().should('have.attr','id','closeModal'); cy.get('body').type('{esc}'); cy.get('#modal').should('not.be.visible'); cy.focused().should('have.id','openModal');

Concrete mini-how-to’s

Making a custom select keyboard accessible

<div role="listbox" tabindex="0" aria-activedescendant="opt1" id="listbox"><div id="opt1" role="option" aria-selected="false">Optie 1</div><div id="opt2" role="option" aria-selected="false">Optie 2</div></div><br><script>const lb = document.getElementById('listbox'); lb.addEventListener('keydown', (e) => { const active = document.getElementById(lb.getAttribute('aria-activedescendant')); if (e.key === 'ArrowDown') { e.preventDefault(); const next = active.nextElementSibling || lb.firstElementChild; active.setAttribute('aria-selected','false'); next.setAttribute('aria-selected','true'); lb.setAttribute('aria-activedescendant', next.id); next.scrollIntoView({block:'nearest'}); } if (e.key === 'Enter') { active.click(); } }); </script>

Quick CSS: zichtbare focus voor alle browsers

:focus { outline: 3px solid #0a84ff; outline-offset: 2px; } :focus:not(:focus-visible) { outline: none; } /* fallback voor browsers zonder :focus-visible */

Praktische testinstructies (kort)

  1. Open pagina, druk Tab tot de footer; noteer on-tabbare elementen.
  2. Controleer modals: open, Tab door, Shift+Tab terug, Escape sluit.
  3. Voer screenreader quick-run (NVDA/VoiceOver) en navigeer met virtual cursor en Tab.
  4. Automatiseer met onze plugin en CI: voeg de plugin toe, run checker, fix violations.

Regelmatig terugkerende fixes

Focus verdwijnt na AJAX update

Zet expliciet focus op het nieuw gegenereerde element: element.focus(); en set tabindex=-1 indien nodig voordat je focust.

Buttons zonder button-tag

Gebruik altijd <button> voor buttons. Als dat echt niet kan: role, tabindex en keyboard handlers toevoegen zoals eerder getoond, maar voorkeursvolgorde: native elements > ARIA-overrides.

Extra resources & tools

  • Gebruik onze WCAG checker/validator voor snelle scans en gedetailleerde foutmeldingen.
  • Download onze plugin op /plugin voor integratie in je devtools en CI.
  • Vragen? Gebruik ons contactformulier—antwoord binnen 24 uur.

Praktische tip: voeg deze korte JS-check toe aan je debug-build om onbereikbare focusable elements te loggen en direct te fixen:

Array.from(document.querySelectorAll('[tabindex="-1"], [tabindex]')).forEach(el => { const t = el.getAttribute('tabindex'); if (t && parseInt(t) < 0) console.warn('Negatieve tabindex gevonden', el); });

Test je website nu met onze WCAG checker/validator, download de plugin via /plugin en stuur vragen via het contactformulier—wij reageren binnen 24 uur.

Previous Post Next Post

Geef een reactie

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