Toegankelijke interactieve elementen: praktische implementatie (WCAG)
Interactie-elementen zoals knoppen, links, modals en formulieren gaan in de praktijk vaak mis: verkeerde focusafhandeling, ontbrekende ARIA/semantiek, onduidelijke visuele focus en ontoegankelijke keyboardflows. Dat veroorzaakt slechte gebruikerservaring en medische juridische risico’s.
Wij helpen developers, frontend engineers, UX/UI designers en redacties met concrete stappen, werkende code-snippets en testbare oplossingen zodat je site of app direct voldoet aan WCAG. Test je werk meteen via onze WCAG checker, download onze plugin of vraag hulp via ons contactformulier (antwoord binnen 24 uur gegarandeerd).
Het probleem in de praktijk
Veelvoorkomende fouten die we in audits en code reviews zien:
- Interactie-elementen gebruiken divs/span zonder rol/keyboard handlers.
- Visuele focus ontbreekt of is onzichtbaar door custom styles.
- Modals en dialogs verstoorden focus-orde en screenreader-ervaring.
- Formulieren missen expliciete labels, aria-describedby voor fouten en geen inline feedback.
- Dynamic content updates hebben geen ARIA-live of worden niet geïdentificeerd voor assistive tech.
Concrete voorbeelden van wat fout gaat
Voorbeeldfouten:
- Clickable card als <div onclick> zonder tabindex/role: niet focusbaar voor keyboard.
- Custom select met alleen click-events: geen keyboard support (arrow keys, home/end).
- Modal die geen focus terugzet bij sluiten: keyboard gebruiker blijft vastzitten.
Zo los je dit op in code
1) Gebruik semantische elementen altijd eerst
Stap 1: gebruik <button> voor acties, <a href> voor navigatie. Pas ARIA alleen toe als semantiek niet toereikend is.
<!-- juist -->
<button class="btn-primary">Opslaan</button>
<a href="/download" class="link">Downloaden</a>
2) Maak custom controls keyboard- en screenreader-vriendelijk
Als je toch custom elementen nodig hebt: rol, tabindex, keyboard handlers en ARIA states verplicht.
<div role="button" tabindex="0" aria-pressed="false" class="card-action">
<h3>Kaart titel</h3>
</div>
/* JS */
const card = document.querySelector('.card-action');
card.addEventListener('click', () => toggle(card));
card.addEventListener('keydown', e => { if(e.key==='Enter' || e.key===' ') { e.preventDefault(); toggle(card); }});
function toggle(el){ const pressed = el.getAttribute('aria-pressed') === 'true'; el.setAttribute('aria-pressed', String(!pressed)); }
3) Focusstijl: zichtbaar en custom-proof
CSS reset kan focus wegnemen. Gebruik duidelijke, contrasterende outline of box-shadow en zichtbaarheid bij :focus en :focus-visible.
/* Goede focusstijl */
:focus-visible { outline: 3px solid #0a84ff; outline-offset: 3px; box-shadow: 0 0 0 3px rgba(10,132,255,0.15); }
/* Vermijd alleen outline: none voor mousetargets */
button, a, [role="button"], input, select, textarea { outline: none; }
button:focus, a:focus, [role="button"]:focus { outline: 3px solid #0a84ff; }
4) Toegankelijke modals/dialogs
Gebruik role=”dialog”, aria-modal=”true”, focus trapping en focus terugzetten. Voorbeeld minimale implementatie:
<div id="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title" hidden>
<h2 id="modal-title">Instellingen</h2>
<button id="closeModal">Sluiten</button>
</div>
/* JS: focus trap & terugzetten */
const modal = document.getElementById('modal');
const openBtn = document.getElementById('openModal');
let lastFocused;
openBtn.addEventListener('click', ()=>{ lastFocused = document.activeElement; modal.hidden = false; modal.querySelector('button').focus(); document.body.style.overflow='hidden'; trapFocus(modal); });
document.getElementById('closeModal').addEventListener('click', ()=>{ modal.hidden = true; lastFocused.focus(); document.body.style.overflow=''; releaseFocusTrap(); });
/* Implementatie van trapFocus/releaseFocusTrap: focus op tab-loop binnen modal */
5) Formulieren: labels, errors en accessible hints
Gebruik expliciete <label for>, aria-describedby voor fout- en helpertekst, en aria-invalid bij fouten.
<label for="email">E-mail</label>
<input id="email" name="email" type="email" aria-describedby="emailHelp emailError">
<div id="emailHelp">We gebruiken dit alleen om je account te identificeren.</div>
<div id="emailError" role="alert" aria-live="assertive"></div>
/* JS: validatie */
const email = document.getElementById('email');
email.addEventListener('blur', ()=>{ const ok = /\S+@\S+\.\S+/.test(email.value); const err = document.getElementById('emailError'); if(!ok){ email.setAttribute('aria-invalid','true'); err.textContent='Vul een geldig e-mailadres in.'; } else { email.removeAttribute('aria-invalid'); err.textContent=''; } });
6) Dynamische updates en ARIA-live
Gebruik aria-live voor zaken die visueel verschijnen zonder focusverandering (foutmeldingen, zoekresultaten).
<div id="status" aria-live="polite"></div>
function updateStatus(msg){ document.getElementById('status').textContent = msg; }
Checklist voor developers
Gebruik deze checklist tijdens development en code reviews. Test na elke change met onze WCAG checker en met automated tools zoals axe-core.
- Semantiek: gebruik native elementen waar mogelijk (button, a, input).
- Keyboard: alle interactieve items zijn focusbaar en werkbaar met Enter/Space/Arrow/Home/End.
- Focus: zichtbare, contrasterende focus-indicatoren voor alle elementen.
- ARIA: alleen waar nodig; juiste roles, aria-label/aria-labelledby, aria-describedby en aria-live.
- Modals: aria-modal, focus trap en focus terugzetten.
- Forms: labels gekoppeld met for/id, aria-invalid en duidelijke foutmeldingen met role=”alert”.
- Contrast: tekst- en focuscontrast voldoen aan WCAG AA/AAA waar van toepassing.
- Automated tests: run axe-core/Pa11y in CI en handmatige toetsen met screenreader/keyboard.
CI snippet: axe-core in Node
const { AxePuppeteer } = require('axe-puppeteer');
const puppeteer = require('puppeteer');
(async ()=>{ const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://jouwsite.example'); const results = await new AxePuppeteer(page).analyze(); console.log(results.violations); await browser.close(); })()
Tips voor designers en redacties
1) Design system regels
Definieer component-gedrag: welke semantische tag, focusstijl, states en aria-attributen. Voeg code snippets toe aan het component-lab.
2) Contrast en kleurgebruik
Gebruik kleurvariabelen en test contrast tijdens design. Voor bodytekst streef naar minimaal WCAG AA (4.5:1), grote tekst 3:1. Gebruik onze checker om kleuren direct te scannen.
3) Copywriting voor interface
Gebruik korte, duidelijke labels, call-to-action teksten en foutmeldingen. Voeg instructies in helper-tekst gekoppeld met aria-describedby.
Hoe test je dit?
Snel handmatig: keyboard-only
- Verwijder muis uit gebruik en navigeer met Tab/Shift+Tab.
- Controleer dat alle interactieve elementen focusbaar en bedienbaar zijn.
- Gebruik Enter/Space voor buttons, pijltjestoetsen voor custom lists/selects.
Screensreader checks
- Gebruik NVDA (Windows), VoiceOver (macOS/iOS) of TalkBack (Android).
- Luister of labels, rollen en foutmeldingen correct aangekondigd worden bij interactie en fouten.
Automated tooling
Voer onze WCAG checker uit voor directe feedback, voeg onze plugin toe in je browser via download of draai axe-core in CI. Voor regressie: screenshot-based contrast checks en keyboard-flow tests.
/* Voorbeeld Pa11y in npm script */
"scripts": {
"accessibility": "pa11y https://localhost:3000 --reporter html --output report.html"
}
Test-cases die je altijd moet draaien
- Modal open/close focus terugzetten.
- Formulier foutflow: submit zonder invullen en check aria-invalid + role=”alert”.
- Keyboard-only complete taak (search, sort, pagination).
- Screenreader: lees order van belangrijke pagina-secties.
Loop na tests direct met fixes door en her-test met onze checker. Vragen? Gebruik ons contactformulier; we reageren binnen 24 uur.
Praktische mini-how-to’s en code-snippets
Accessible skip-link
<a class="skip-link" href="#main">Direct naar inhoud</a>
<style>.skip-link{position:absolute;left:-999px;top:auto;width:1px;height:1px;overflow:hidden} .skip-link:focus{position:static;width:auto;height:auto;left:0;}
Keyboard-navigatie voor custom dropdown
/* Simplified example */
<div class="dropdown" role="listbox" tabindex="0" aria-activedescendant="opt1">
<div id="opt1" role="option" tabindex="-1">Optie 1</div>
<div id="opt2" role="option" tabindex="-1">Optie 2</div>
</div>
/* JS: pijltjes naar volgende/vorige optie en update aria-activedescendant */
Contrast-check snippet (JS)
function luminance(r,g,b){const a=[r,g,b].map(v=>{v/=255;return v<=0.03928? v/12.92 : Math.pow((v+0.055)/1.055,2.4)});return 0.2126*a[0]+0.7152*a[1]+0.0722*a[2];}
function contrast(rgb1, rgb2){const lum1=luminance(...rgb1)+0.05;const lum2=luminance(...rgb2)+0.05;return Math.max(lum1,lum2)/Math.min(lum1,lum2);}
console.log(contrast([255,255,255],[17,24,39]));