Alt-teksten en afbeeldingen: richtlijnen en voorbeelden

Praktische gids: Focusmanagement en keyboard-toegankelijkheid — wcagtool.nl

Focusmanagement en keyboard-toegankelijkheid in de praktijk

Focusmanagement en keyboard-toegankelijkheid gaan in de praktijk vaak mis: tabvolgorde klopt niet, modals vangen focus verkeerd op, custom controls missen ARIA of tabindex-advies en visuele focusstijl is afwezig. Dat leidt tot frustratie bij toetsenbordgebruikers en faalt tegen WCAG 2.1/2.2-criteria (2.1.1 Keyboard, 2.4.3 Focus order, 2.4.7 Focus visible).

Wij lossen dit praktisch op met concrete patronen, herbruikbare code-snippets en testbare stappen die ontwikkelaars, designers en redacteurs direct kunnen toepassen. Test je implementatie altijd direct met onze WCAG checker/validator, download onze plugin voor CI-integratie en vraag ons advies via het contactformulier — vragen worden binnen 24 uur beantwoord.

Het probleem in de praktijk

Veelvoorkomende fouten

  • Gebruik van tabindex=”0″ en tabindex=”-1″ zonder duidelijke regels, waardoor tabvolgorde onvoorspelbaar wordt.
  • Modals en dialogs vangen focus niet netjes (geen focustrap of geen terugzetten van focus).
  • Custom widgets (dropdowns, sliders, accordions) missen keyboard support of ARIA attributen.
  • Geen of slechte focus-styling: outline: none; zonder alternatief.
  • Skip-links ontbreken of zijn onzichtbaar.

Waarom dit slecht is voor gebruikers

Toetsenbordgebruikers, assistive technology gebruikers en power users verliezen context en raken ‘vast’ of missen content. Automatische testtools vinden onderdelen, maar alleen praktische keyboard-tests en screener-interacties valideren echte toegankelijkheid.

Zo los je dit op in code

1) Basisregels voor tabindex

Regels:

  • Gebruik tabindex alleen waar nodig. Geef de voorkeur aan natuurlijke focusbare elementen (<button>, <a href>, <input>).
  • tabindex=”0″ voegt element toe aan tabvolgorde (gebruik spaarzaam voor custom controls).
  • tabindex=”-1″ maakt element focusbaar via script (gebruik voor focus management zoals terugzetten van focus).

Voorbeeld: skip-link (HTML + CSS)

<a class="skip-link" href="#main">Overslaan naar hoofdinhoud</a><!-- plaats als eerste element in <body> -->
.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;outline:3px solid #0a84ff;padding:4px 8px;background:#fff;z-index:1000}

2) Focus zichtbaar maken (CSS)

Gebruik :focus-visible voor moderne browsers en fallback voor oudere browsers. Verwijder nooit focus outlines zonder goed alternatief.

/* moderne browsers */:focus-visible{outline:3px solid #0a84ff;outline-offset:2px;border-radius:3px}/* fallback */:focus{outline:3px solid rgba(10,132,255,.9);outline-offset:2px}

3) Modal/dialog: focustrap en terugzetten van focus (HTML + JS)

Pattern: wanneer modal opent, bewaar actieve element, zet focus op eerste focusbaar element in modal, trap focus binnen modal, bij sluiten zet focus terug en verwijder inert/aria-hidden op rest van page.

<!-- HTML --><button id="openModal">Open modal</button><div id="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle" hidden><h2 id="modalTitle">Titel</h2><button id="closeModal">Sluiten</button></div>
// JavaScript: focustrap en terugzetten (eenvoudige implementatie)
const openBtn = document.getElementById('openModal');
const modal = document.getElementById('modal');
const closeBtn = document.getElementById('closeModal');
let lastFocus;
function getFocusable(container){return Array.from(container.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex^=\"-\"])')).filter(el=>el.offsetParent!==null)}
function trapFocus(e){
 const focusable = getFocusable(modal);
 if(focusable.length===0){e.preventDefault();return}
 const first = focusable[0], 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()}
 }
}
openBtn.addEventListener('click', ()=>{lastFocus = document.activeElement; modal.hidden=false; document.body.setAttribute('aria-hidden','true'); modal.removeAttribute('aria-hidden'); const focusable = getFocusable(modal); (focusable[0]||closeBtn).focus(); document.addEventListener('keydown', trapFocus);});
closeBtn.addEventListener('click', ()=>{modal.hidden=true; document.body.removeAttribute('aria-hidden'); lastFocus?.focus(); document.removeEventListener('keydown', trapFocus);});

Opmerking over inert/aria-hidden

Gebruik inert waar mogelijk of zet aria-hidden=”true” op de rest van de content. Bibliotheken zoals focus-trap of inert polyfills bieden robuuste oplossingen.

4) Custom controls: voorbeeld van een toegankelijke dropdown

<div class="dropdown" role="combobox" aria-haspopup="listbox" aria-expanded="false">
  <button class="dropdown-toggle" aria-controls="drp1" id="drpToggle">Kies optie</button>
  <ul id="drp1" role="listbox" tabindex="-1" hidden>
    <li role="option" data-value="1">Eén</li>
    <li role="option" data-value="2">Twee</li>
  </ul>
</div>
// JS: keyboard interactions (Arrow keys, Enter, Escape)
const toggle = document.getElementById('drpToggle');
const list = document.getElementById('drp1');
toggle.addEventListener('click', ()=>{const open = toggle.getAttribute('aria-expanded')==='true'; toggle.setAttribute('aria-expanded', String(!open)); list.hidden = open; if(!open){list.focus()}});
list.addEventListener('keydown', e =>{
 const items = Array.from(list.querySelectorAll('[role=\"option\"]'));
 const idx = items.indexOf(document.activeElement);
 if(e.key==='ArrowDown'){e.preventDefault(); const next = items[idx+1]||items[0]; next.focus()}
 if(e.key==='ArrowUp'){e.preventDefault(); const prev = items[idx-1]||items[items.length-1]; prev.focus()}
 if(e.key==='Enter'){document.activeElement.click()}
 if(e.key==='Escape'){toggle.click(); toggle.focus()}
});

5) Formulieren: duidelijke focus en foutnavigatie

Zorg dat labels linked zijn (for/id), gebruik aria-invalid en aria-describedby voor foutmeldingen en verplaats focus naar eerste fout bij submit.

// focus naar eerste fout (voorbeeld)
const form = document.querySelector('form');
form.addEventListener('submit', e =>{
 const firstError = form.querySelector('.error');
 if(firstError){e.preventDefault(); firstError.focus(); firstError.scrollIntoView({behavior:'smooth',block:'center'})}
});

Checklist voor developers

  • Tabvolgorde volgt visuele volgorde: vermijd positionering die de DOM-volgorde breekt.
  • Gebruik semantische elementen (<button>, <a>). Gebruik ARIA alleen als semantiek niet volstaat.
  • Elke interactieve component heeft keyboard-ondersteuning (Enter, Space, Arrow keys indien relevant, Escape voor sluiten).
  • Focusstyles zijn zichtbaar en contrastrijk (gebruik :focus-visible).
  • Modals: focustrap, aria-modal/role=dialog, focus terugzetten op opener.
  • Custom widgets: correcte role, aria-expanded/aria-checked/aria-selected en keyboard events.
  • Gebruik getFocusable helper in utilities en test met document.activeElement in unit-tests.
  • Voeg e2e tests toe (Cypress + cypress-axe) en run je site door onze WCAG checker/validator tijdens CI.

Tips voor designers en redacties

Visuele focusstijl ontwerpen

Houd focusstijl consistent en zichtbaar: geen subtiele kleurveranderingen. Designelementen moeten focusstaten in componentenbibliotheek bevatten (hover, focus, active).

Content en headertags

Redacties: gebruik betekenisvolle linkteksten (geen “klik hier”), zorg dat interactieve content als zodanig wordt gemarkeerd (buttons vs links). Voor lange pagina’s: voeg skip-link en duidelijke subkop-structuur toe.

Design tokens voor accessibility

Definieer kleuren en focus-radii als tokens zodat devs makkelijk :focus-visible kunnen stylen met consistente tokens. Documenteer keyboard-flows in component specs.

Hoe test je dit?

Handmatige keyboard-tests (stap-voor-stap)

  1. Schakel muis uit (of leg je handen op toetsenbord). Navigeer via Tab en Shift+Tab door de pagina. Volg visueel de focus en controleer of volgorde logisch is.
  2. Open en sluit modals via keyboard. Controleer dat focus in modal blijft en teruggezet wordt naar opener.
  3. Test custom controls: gebruik Enter/Space/Arrow/Escape. Controleer aria-attributes met accessibility inspector.
  4. Gebruik skip-link en controleer of deze zichtbaar en bruikbaar is.

Screenreader-tests

Test met NVDA (Windows) en VoiceOver (macOS/iOS). Controleer of roles, labels en statusupdates (aria-live) correct worden aangekondigd. Voor modals: controleer dat de screenreader niet de achterliggende content leest terwijl modal open is.

Automated & CI-tests

Integreer axe-core: lokaal met axe DevTools, in CI met jest-axe of in e2e met cypress-axe. Draai onze online WCAG checker/validator na elke deploy.

// Cypress + cypress-axe voorbeeld
cy.visit('/page-with-modal');
cy.injectAxe();
cy.checkA11y(null, {includedImpacts:['critical','serious']});

Unit-test focusmanagement (voorbeeld met Jest)

// mocked DOM test: expect focus to return to opener after modal close
test('focus returns to opener', ()=>{document.body.innerHTML = '<button id=\"open\">Open</button><div id=\"modal\" hidden><button id=\"close\">Close</button></div>'; const open = document.getElementById('open'); const modal = document.getElementById('modal'); const close = document.getElementById('close'); open.focus(); // simulate open closeModal(); expect(document.activeElement).toBe(open);});

Gebruik onze tools

Test direct je pagina met onze WCAG checker/validator: <a href=”https://wcagtool.nl/checker”>WCAG checker/validator</a>. Download onze plugin voor automatische scans in CI: <a href=”https://wcagtool.nl/plugin”>Download Plugin</a>. Zit je vast? Stel je vraag via <a href=”https://wcagtool.nl/contact”>contactformulier</a> — we beantwoorden binnen 24 uur.

Praktische mini-how-to’s en code-snippets

Helper: getFocusable()

function getFocusable(container=document){return Array.from(container.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex]:not([tabindex^=\"-\"])')).filter(el => el.offsetWidth>0 || el.offsetHeight>0 || el.getClientRects().length)}

ARIA-snackbar: animeren zonder focusverlies

<div role="status" aria-live="polite" aria-atomic="true" id="toast">Opgeslagen</div>

Kort: wanneer wel/niet ARIA gebruiken

  • Gebruik semantische HTML altijd eerst.
  • Gebruik ARIA als element functioneel afwijkt van standaard, of als semantiek ontbreekt.
  • Onderhoud ARIA-states in JS (aria-expanded, aria-pressed, aria-selected).

Tips voor snelle implementatie in bestaande projecten

  • Voeg een globale :focus-visible stylesheet en skip-link toe als eerste PR — grote impact, weinig werk.
  • Maak een kleine modal-helper-module (open, close, trapFocus, restoreFocus) en vervang alle losse modals met deze module.
  • Voeg keyboard smoke-tests toe aan je e2e-suite die tabvolgorde en modals doorlopen.
  • Implementeer aria-hidden/inert op page container wanneer je overlays opent.

Test je site direct met onze tool: <a href=”https://wcagtool.nl/checker”>Start scan</a>. Download onze plugin voor automatische validatie: <a href=”https://wcagtool.nl/plugin”>Plugin downloaden</a> en stuur vragen via <a href=”https://wcagtool.nl/contact”>contactformulier</a> — antwoord binnen 24 uur.

Laatste tip: voeg dit korte script toe aan je component library om visuele regressie van focus te detecteren in visuele tests (Selenium/Percy/Cypress):

// Detect missing focus styles: focus element then take screenshot
const el = document.querySelector('.your-component button');
el.focus();
const hasFocusOutline = window.getComputedStyle(el).getPropertyValue('outline-style')!=='none' || window.getComputedStyle(el).getPropertyValue('box-shadow')!=='none';
if(!hasFocusOutline){console.warn('Geen zichtbare focusstijl op', el);} 

Previous Post Next Post

Geef een reactie

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