Video’s ondertitelen en audiodescriptie toevoegen

Praktische WCAG-implementatie – wcagtool.nl

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

  1. Toetsenbord-navigatie: navigeer alleen met Tab, Shift+Tab, Enter en Space. Kun je alle functies bedienen?
  2. Screenreader: test met NVDA/JAWS op Windows of VoiceOver op macOS/iOS. Zijn labels en statusmeldingen begrijpelijk?
  3. Zoom & responsive: vergroot 200% en controleer layout en interacties.
  4. Contrast: test kritische pagina's met contrastchecker of onze WCAG checker.
  5. 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)

  1. Voer site-scan uit met onze WCAG checker/validator en exporteer violations.
  2. Prioriteer: 1) keyboard & focus 2) forms & errors 3) color & contrast 4) semantiek.
  3. Implementeer fixes per component met unit-tests en axe of accessibility regression tests in CI.
  4. Role-based QA: geef designers en redacteurs toegang tot de plugin zodat fouten vroeg in de contentflow verdwijnen.
  5. 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>