De vier principes van WCAG: waar het om draait

Praktische implementatie: Toegankelijke interactieve elementen (WCAG) | WCAGTool

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

  1. Verwijder muis uit gebruik en navigeer met Tab/Shift+Tab.
  2. Controleer dat alle interactieve elementen focusbaar en bedienbaar zijn.
  3. Gebruik Enter/Space voor buttons, pijltjestoetsen voor custom lists/selects.

Screensreader checks

  1. Gebruik NVDA (Windows), VoiceOver (macOS/iOS) of TalkBack (Android).
  2. 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]));

Direct aan de slag: test je pagina nu met onze WCAG checker, installeer de plugin of stuur je vraag via ons contactformulier — we reageren binnen 24 uur. Laat hieronder een snelle check zien: voer één probleem op, plak je HTML en wij geven een concrete fix binnen 24 uur.

Laatste praktische tip: voeg in je component-library een "accessibility checklist" checkbox die developers verplicht te controleren (semantiek, keyboard, focus, ARIA, contrast). Hier een minimale HTML-check die je in PR-templates kunt opnemen:

- [ ] Native semantics gebruikt waar mogelijk (button/a/input)
- [ ] Keyboard bedienbaar (Tab/Enter/Space/Arrows)
- [ ] Focus zichtbaar met :focus-visible
- [ ] ARIA alleen als fallback, juiste roles/labels
- [ ] Modals trap focus en zetten focus terug
- [ ] Formulieren tonen inline fouten met role="alert"