WCAG praktisch toegepast — snelle, testbare oplossingen
Toepassen van WCAG faalt in de praktijk vaak omdat implementaties oppervlakkig blijven: juiste ARIA-attributen ontbreken, focus-management is slecht of content-editors leveren niet-toegankelijke teksten. Wij lossen dit op met concrete code, stap-voor-stap testinstructies en tooling waarmee je direct je site controleert.
Dit artikel is bedoeld voor developers, frontend engineers, UX/UI designers en redacteurs die meteen willen implementeren. Je krijgt heldere code-snippets (HTML/ARIA/CSS/JS), checklists en teststappen. Test je site direct met onze WCAG checker/validator, download onze plugin en neem contact op via het contactformulier (antwoord binnen 24 uur).
Het probleem in de praktijk
Gebrekkige keyboard-toegankelijkheid
Veel componenten lijken te werken met de muis maar zijn onbruikbaar met toetsenbord (vakjes zonder focus, custom controls zonder role/tabindex, modals zonder focus-trap).
Onvoldoende focus- en zichtbaarheid
Stijlregels verwijderen standaard focus-states of kunnen focus onzichtbaar maken op high-contrast of zoomniveau.
Slechte semantiek en ARIA-misbruik
Developers gebruiken aria-hidden, role of aria-* verkeerd waardoor schermlezers verwarring geven of content onzichtbaar wordt voor hulptechnologie.
Redactionele fouten: labels, alt-teksten en kopstructuur
Content-editors vullen geen alt-teksten of gebruiken koppen niet hiërarchisch — dit breekt navigatie voor schermlezergebruikers.
Zo los je dit op in code
Keyboard-first: maak interactieve elementen echte controls
Gebruik native elementen waar mogelijk. Als je custom controls nodig hebt, bouw ze met role, tabindex en keyboard-handlers.
<!-- Slecht: div als knop -->
<div class="btn">Opslaan</div>
<!-- Goed: native button of ARIA met keyboard -->
<button class="btn">Opslaan</button>
<!-- Custom control voorbeeld -->
<div role="button" tabindex="0" aria-pressed="false" id="favToggle">Favoriet</div>
<script>
var btn = document.getElementById('favToggle');
btn.addEventListener('click', function(){ toggle(); });
btn.addEventListener('keydown', function(e){
if(e.key==='Enter' || e.key===' ') { e.preventDefault(); toggle(); }
});
function toggle(){
var p = btn.getAttribute('aria-pressed')==='true';
btn.setAttribute('aria-pressed', String(!p));
}
</script>
Focus management: zichtbare, consistente focus-staten
Verwijder nooit outline zonder alternatief. Gebruik duidelijke, contrastrijke focusstijlen en beheer focus bij dynamische UI (modals, single-page routes).
/* CSS: behoud zichtbare focus */
:focus { outline: 3px solid #ffbf47; outline-offset: 3px; }
/* Specifieke styling voor interactieve components */
button:focus, a:focus { box-shadow: 0 0 0 3px rgba(11,120,166,0.2), 0 0 0 3px #ffbf47 inset; }
Modal met focus-trap en accessible labeling
Altijd aria-modal, role en focus terugzetten naar trigger bij sluiten. Trap focus binnen modal.
<!-- HTML -->
<button id="openModal">Open dialog</button>
<div id="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle" hidden>
<h2 id="modalTitle">Bevestigen</h2>
<button id="closeModal">Sluiten</button>
</div>
<script>
const open = document.getElementById('openModal');
const modal = document.getElementById('modal');
const close = document.getElementById('closeModal');
let lastFocused;
open.addEventListener('click', ()=>{ lastFocused=document.activeElement; modal.hidden=false; close.focus(); document.body.style.overflow='hidden'; });
close.addEventListener('click', ()=>{ modal.hidden=true; lastFocused.focus(); document.body.style.overflow=''; });
// Eenvoudige focus trap
modal.addEventListener('keydown', (e)=>{
if(e.key==='Tab'){
const focusable = modal.querySelectorAll('a[href],button,input,select,textarea,[tabindex]:not([tabindex="-1"])');
if(focusable.length===0) return;
const first = focusable[0], 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>
Kleurcontrast: dynamische check en CSS fallback
Gebruik berekeningen en wees pragmatisch: controleer ratio, pas variabelen aan en geef editors tools.
/* Voorbeeld CSS-variabelen */
:root{ --brand:#0b78a6; --brand-text:#fff; }
.btn-contrast{ background:var(--brand); color:var(--brand-text); }
/* JS: eenvoudige contrastcheck (luminantie) */
function luminance(hex){
const c = hex.replace('#','');
const r = parseInt(c.substr(0,2),16)/255;
const g = parseInt(c.substr(2,2),16)/255;
const b = parseInt(c.substr(4,2),16)/255;
const srgb=[r,g,b].map(v=> v<=0.03928 ? v/12.92 : Math.pow((v+0.055)/1.055,2.4));
return 0.2126*srgb[0]+0.7152*srgb[1]+0.0722*srgb[2];
}
function contrast(hex1,hex2){
const L1 = luminance(hex1), L2 = luminance(hex2);
return (Math.max(L1,L2)+0.05)/(Math.min(L1,L2)+0.05);
}
console.log('Contrast', contrast('#ffffff','#0b78a6'));
Forms: labels, errors en aria-describedby
Altijd label-elementen, gebruik aria-describedby voor foutmeldingen en aria-invalid voor invalide velden.
<label for="email">E-mail</label>
<input id="email" name="email" type="email" aria-describedby="emailHelp emailError">
<small id="emailHelp">We gebruiken je e-mail alleen voor facturatie.</small>
<div id="emailError" role="alert" aria-live="assertive" hidden>Voer een geldig e-mailadres in.</div>
<script>
const input = document.getElementById('email');
input.addEventListener('blur', function(){
const valid = input.value.includes('@');
const err = document.getElementById('emailError');
if(!valid){ input.setAttribute('aria-invalid','true'); err.hidden=false; }
else { input.removeAttribute('aria-invalid'); err.hidden=true; }
});
</script>
Dynamische content en ARIA-live regions
Gebruik aria-live voor updates die niet automatisch de focus veranderen (bijv. notificaties), en beperk assertive gebruik tot kritieke meldingen.
<div id="toast" aria-live="polite" aria-atomic="true"></div>
<script>
function showToast(msg){
const t = document.getElementById('toast');
t.textContent = msg;
// screenreaders lezen dit voor
}
</script>
Checklist voor developers
- Gebruik native HTML-controls waar mogelijk (buttons, inputs, selects).
- Alle interactieve elementen hebben focus (tabindex) en toetsenbord-ondersteuning.
- Focus-states zijn zichtbaar en consistent; geen outline: none zonder alternatief.
- ARIA alleen gebruiken om semantiek uit te breiden, niet te vervangen. Controleer roles en properties.
- Modal/dialogs hebben aria-modal, zijn focus-trapped en zetten focus terug naar trigger.
- Formulieren: labels aanwezig, aria-describedby voor foutberichten, aria-invalid bij fouten.
- Afbeeldingen hebben betekenisvolle alt-teksten; decoratieve afbeeldingen aria-hidden="true" of empty alt (<img alt="">).
- Kleurcontrast >= 4.5:1 (body tekst) en >= 3:1 voor grotere tekst/graphics.
- ARIALive regions voor asynchrone updates en role="alert" bij directe errors.
- Voer geautomatiseerde en handmatige tests uit (zie sectie hieronder) en fix issues met onze WCAG checker/validator.
Tips voor designers en redacties
Gebruik een toegankelijke designsystem startkit
Definieer componenten met aria-compatibele states en voorbeeld copy. Zorg dat kleurvarianten getest worden met contrasttools.
Contentrichtlijnen voor redacteurs
- Alt-tekst: beschrijf doel en functie, niet uiterlijk ("Foto van..." alleen als relevant).
- Koppenstructuur: H1 bovenaan, H2 voor secties, H3 voor subsecties; geen visuele headers zonder semantische tag.
- Linktekst: vermijd "klik hier"; gebruik duidelijke beschrijvingen.
- Gebruik kort en eenvoudig taalgebruik; voeg samenvattingen toe voor ingewikkelde content.
Interactie en microcopy
Voorzie formuliervelden van duidelijke voorbeelden en validatieberichten. Zet placeholders niet als vervanging voor labels.
Hoe test je dit?
Automatisch testen — tools en commando's
- Start met onze WCAG checker/validator voor snelle scan en prioriteitslijst. Gebruik de plugin (download via pluginpagina) voor in-browser feedback tijdens development.
- Integratie in CI: gebruik axe-core als Node dependency voor unit/CI tests. Voorbeeld npm script:
// Install: npm i -D axe-core puppeteer
// Voorbeeld Node test (kort)
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.test');
const results = await new AxePuppeteer(page).analyze();
console.log(results.violations.length, 'violations');
await browser.close();
})();
Handmatig testen — snelle checklist
- Toetsenbord-navigatie: navigeer alleen met Tab, Shift+Tab, Enter en Space. Kun je alle functies bedienen?
- Screenreader: test met NVDA/JAWS op Windows of VoiceOver op macOS/iOS. Zijn labels en statusmeldingen begrijpelijk?
- Zoom & responsive: vergroot 200% en controleer layout en interacties.
- Contrast: test kritische pagina's met contrastchecker of onze WCAG checker.
- Formulieren: voer foutieve invoer en controleer aria-invalid en focus van foutberichten.
Testcases die je direct kunt draaien
1) Open homepage, druk Tab 20x: noteer of alles focus krijgt.
2) Open modal, druk Tab en Shift+Tab: focus mag niet buiten modal.
3) Schakel afbeeldingen uit (of gebruik browser devtools) en controleer of alt-teksten voldoende zijn.
4) Voer invalid email in formulier en check of error role="alert" en aria-describedby werken.
Gebruik onze validator: https://wcagtool.nl — upload of scan je URL voor prioriteit en fix-steps.
Praktische integratie-stappen (sprint-ready)
- Voer site-scan uit met onze WCAG checker/validator en exporteer violations.
- Prioriteer: 1) keyboard & focus 2) forms & errors 3) color & contrast 4) semantiek.
- Implementeer fixes per component met unit-tests en axe of accessibility regression tests in CI.
- Role-based QA: geef designers en redacteurs toegang tot de plugin zodat fouten vroeg in de contentflow verdwijnen.
- Her-test met onze tool en stuur ons een screenshot/rapport via het contactformulier — wij reageren binnen 24 uur met concrete next steps.
Laatste praktische tip
Gebruik dit compacte snippet als basis voor toegankelijke toggles in je design system — kopieer, test met keyboard en screenreader en registreer je component in het systeem. Test je pagina vervolgens meteen met onze WCAG checker/validator of installeer de plugin voor realtime feedback. Vragen? Gebruik ons contactformulier — antwoord binnen 24 uur.
<!-- Reuseable accessible toggle component -->
<button class="toggle" role="switch" aria-checked="false" aria-label="Nieuwsbrief aan/uit" id="newsToggle">Uit</button>
<script>
document.getElementById('newsToggle').addEventListener('click', function(){
const state = this.getAttribute('aria-checked') === 'true';
this.setAttribute('aria-checked', String(!state));
this.textContent = state ? 'Uit' : 'Aan';
});
</script>