De WCAG principes uitgelegd: Waarneembaar, Bedienbaar, Begrijpelijk, Robuust

WCAG praktisch: Keyboard en focus management — wcagtool.nl

Keyboard & focus management: praktisch toepassen volgens WCAG

Keyboard- en focusproblemen zijn een van de meest voorkomende toegankelijkheidsfouten: onzichtbare focus, keyboard traps, niet-toegankelijke modals en custom controls zonder ARIA zorgen ervoor dat mensen met toetsenbord of screenreader vastlopen. In de praktijk missen teams vaak concrete implementatieregels en testen ze niet systematisch.

Wij helpen je met duidelijke stappen, kant-en-klare code-snippets en testbare oplossingen die je direct in je codebase kunt plakken. Test je werk tussentijds met onze WCAG checker/validator, download onze plugin voor continu inzicht en stel vragen via het contactformulier (antwoord binnen 24 uur).

Het probleem in de praktijk

Veel teams realiseren zich te laat dat keyboardgebruikers andere verwachtingen hebben: tab-volgorde moet logisch zijn, focus moet zichtbaar en voorspelbaar zijn, modals moeten focus beheren en custom widgets moeten ARIA en keyboard-events bieden. Typische fouten:

  • Geen zichtbare focus of focus in transparante elementen
  • Tabvolgorde die afwijkt van visuele volgorde
  • Custom controls zonder role, tabindex of keyboard handlers
  • Modals die focus niet terugzetten bij sluiten of keyboard traps creëren
  • Gebruik van tabindex=”0/1/…” zonder duidelijke reden

Deze fouten veroorzaken ernstige gebruiksproblemen voor mensen die geen muis kunnen gebruiken of screenreaders gebruiken — en leveren direct WCAG-kritieke issues op.

Zo los je dit op in code

1) Basisregel: behoud natuurlijke focusvolgorde

Stap-voor-stap:

  1. Houd DOM-volgorde gelijk aan visuele volgorde.
  2. Vermijd tabindex > 0; gebruik tabindex=”0″ alleen om niet-focusbare elementen toch focusbaar te maken.
  3. Gebruik semantische elementen (button, a, input, nav, form) waar mogelijk.
<!-- slecht -->
<div role="button" tabindex="0">Klik hier</div>

<!-- goed -->
<button type="button">Klik hier</button>

2) Zichtbare focusstijl (CSS)

Voeg duidelijke, contrastrijke focusstijlen toe. Niet alleen outline: combineer outline en box-shadow zodat focus zichtbaar blijft bij hoge contrastmodi.

button:focus, a:focus, [tabindex="0"]:focus {outline:3px solid #0a84ff; box-shadow:0 0 0 3px rgba(10,132,255,0.2); border-radius:4px;}

3) Skip-links

Voeg een skip-link toe die direct zichtbaar wordt bij focus, voor keyboard-gebruikers en screenreadergebruikers.

<a href="#main" class="skip-link">Sla navigatie over</a>

4) Toegankelijke modals: focus trapping en terugzetten

Stappen en code om modals correct te implementeren:

  1. Wanneer modal opent: focus naar eerste focusbaar element in de modal.
  2. Trap tab binnen modal (Shift+Tab werkt terug).
  3. Sluit modal op ESC en zet focus terug naar element dat modal opende.
  4. Gebruik role=”dialog” aria-modal=”true” en aria-label/aria-labelledby.
function openModal(modal, trigger){const focusable='a[href],button,input,select,textarea,[tabindex]:not([tabindex="-1"])';const first=modal.querySelectorAll(focusable)[0];modal.dataset.triggerId=trigger.id;modal.style.display='block';modal.setAttribute('aria-hidden','false');first?.focus();document.addEventListener('keydown', trap);function trap(e){if(e.key==='Escape'){closeModal(modal);}if(e.key==='Tab'){const nodes=Array.from(modal.querySelectorAll(focusable));const cur=document.activeElement;const idx=nodes.indexOf(cur);if(e.shiftKey&&idx===0){e.preventDefault();nodes[nodes.length-1].focus();}else if(!e.shiftKey&&idx===nodes.length-1){e.preventDefault();nodes[0].focus();}}}function closeModal(m){m.style.display='none';m.setAttribute('aria-hidden','true');document.removeEventListener('keydown',trap);const triggerId=m.dataset.triggerId;document.getElementById(triggerId)?.focus();}}

5) Custom controls: ARIA en keyboard handlers

Voor custom toggles, checkboxes of select-lijsten: gebruik correct role en beheer keyboard events (Space/Enter voor toggles, Arrow keys voor rolswitches).

<div role="switch" tabindex="0" aria-checked="false" id="mySwitch">Uit</div>

6) Formulierfouten en focus

Zet focus op het eerste foutveld na submit en geef aria-invalid en aria-describedby voor foutboodschappen.

function handleSubmit(e){const invalid=document.querySelector('.error input, .error textarea');if(invalid){e.preventDefault();invalid.focus();invalid.setAttribute('aria-invalid','true');const errId=invalid.getAttribute('aria-describedby');document.getElementById(errId)?.focus();}}

Checklist voor developers

  • DOM-volgorde = visuele volgorde
  • Geen tabindex > 0 gebruiken
  • Alle interactieve elementen zijn focusbaar en hebben zichtbare focus
  • Modals: focus trap + terugzetten + aria-modal
  • Custom controls: juiste role, aria-* attributen en keyboard handlers
  • Skip-link aanwezig en testbaar
  • Form-fouten: focus naar eerste foutveld en aria-invalid
  • Automatische testing: run je pagina door onze WCAG checker na implementatie

Tips voor designers en redacties

Designsystemen en componentbibliotheken

Maak toegankelijkheid onderdeel van component-standaarden: alle buttons, inputs en dialogs die designers aanleveren moeten states, focus-styles en keyboard flows bevatten. Documenteer in je design system voorbeelden met HTML/CSS en keyboard flows.

Content en tekstredactie

Zorg dat linkteksten geen “klik hier” zijn en maak form labels expliciet. Redacteuren: controleer dat interactieve inline-elementen (zoals accordions) echte buttons zijn of correct ARIA hebben.

Kleur en focuscontrasten

Voeg focusstijlen die voldoen aan contrastregels: focusring moet contrasteren met achtergrond en omringende elementen. Gebruik onze checker voor contrast en focus tests: Test nu.

Hoe test je dit?

Handmatige tests (snel)

  1. Tab door de pagina zonder muis: volgorde logisch? Alle interacties bereikbaar?
  2. Gebruik Shift+Tab, Enter, Space, Arrow keys waar relevant.
  3. Open en sluit modals: focus terug naar trigger?
  4. Gebruik screenreader (NVDA/VoiceOver) en navigeer via headings en links.

Automatische en semi-automatische tests

Draai onze WCAG checker/validator op pagina-niveau voor snelle feedback. Integreer onze plugin in je CI-pipeline om regressies te detecteren. Combineer met axe-core of pa11y voor component tests.

Testcases en stappen

  1. Maak een testpagina met: header, skip-link, nav, gemarkeerde interactieve componenten, modal, formulier met validatieregels.
  2. Automatiseer: voeg e2e-tests die keyboard-navigatie simuleren (Playwright of Cypress). Voorbeeld Playwright snippet:
const {test,expect}=require('@playwright/test');test('keyboard navigation',async({page})=>{await page.goto('/testpage');await page.keyboard.press('Tab');await page.keyboard.press('Tab');const focused=await page.evaluate(()=>document.activeElement.id);expect(focused).toBe('expected-element-id');});

Regelmatig testen en 24-uurs support

Test iteratief: na elke change run je de checker en je plugin. Vragen of onduidelijkheden? Gebruik ons contactformulier — wij reageren binnen 24 uur en helpen je met concrete code-aanpassingen.

Praktische tip: voer een korte checklist-run in je pull requests: check focus-states, tab-volgorde en modal-behandeling. Gebruik onze plugin om dit automatisch te valideren bij iedere push: download plugin en koppel aan je CI.

Direct toepasbare test checklist (kopieer/plak)

1. Tab door de pagina; noteer onverwachte stops. 2. Open modal; press Tab en Shift+Tab; press ESC to close. 3. Focus terug op trigger? 4. Trigger custom control met Enter/Space; gedrag correct? 5. Form submit met fouten; focus on first error? 6. Run wcagtool.nl/checker and fix reported keyboard/focus issues.

Als je klaar bent: test je site direct met onze WCAG checker/validator of installeer de plugin voor continue feedback. Voor vragen kun je altijd het contactformulier invullen; we beantwoorden binnen 24 uur.

Laatste praktische check: plak dit korte script in je console op de pagina om snel te controleren of er tabbale elementen buiten zichtbare viewport zitten en wat de tab-volgorde is.

(function(){const focusable='a[href],button,input,select,textarea,[tabindex]:not([tabindex="-1"])';const nodes=Array.from(document.querySelectorAll(focusable));console.table(nodes.map(n=>({tag:n.tagName,id:n.id,tabindex:n.getAttribute('tabindex')||'',text:n.textContent.trim().slice(0,40),visible:!!(n.offsetWidth||n.offsetHeight||n.getClientRects().length)})));})();

Previous Post Next Post

Geef een reactie

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