Verschil tussen WCAG 2.0, 2.1, 2.2 en WCAG 3.0

Toegankelijke formulieren – Praktische implementatie | wcagtool.nl

Toegankelijke formulieren: praktisch toepassen van WCAG

Formulieren zijn in de praktijk een van de grootste valkuilen voor WCAG-compliance: ontbrekende labels, onduidelijke foutmeldingen, slechte focus-sturing en misbruik van ARIA komen continu voor. Ontwerpers en developers vinden het vaak lastig om alle interactiestappen, validatie-feedback en toetsenbordflows echt toegankelijk te maken.

Wij lossen dit praktisch op met duidelijke codepatronen, testbare stappen en herbruikbare snippets die direct in je frontend te plakken zijn. Gebruik onze voorbeelden, test met onze WCAG checker/validator en download onze plugin voor automatische controles; bij vragen reageren we binnen 24 uur via ons contactformulier.

Het probleem in de praktijk

Veelgemaakte fouten

  • Labels niet gekoppeld aan inputs (<label for> ontbreekt of wordt visueel verborgen zonder juiste techniek).
  • Foutmeldingen die niet aan velden gekoppeld zijn (geen aria-describedby/aria-invalid).
  • Geen logische focusvolgorde of geen focus naar foutvelden na submit.
  • Custom controls (selects, toggles) zonder juiste ARIA-rollen en keyboard support.
  • Contrast of visuele signalen als enige indicatie voor validatie.

Waarom dat problemen geeft voor WCAG

Slechte labels en foutafhandeling maken formulieren onbruikbaar voor screenreadergebruikers en toetsenbordgebruikers; gebrek aan focus management zorgt voor onduidelijke flows en verhoogt faalkans. Daarom faalt vaak het succescriterium rondom forms, fouten en assistieve technologie.

Zo los je dit op in code

Basis: juiste label-associatie

Altijd: gebruik <label for=”id”> of een wrapping <label>. Dit is de meest betrouwbare manier.

<label for="email">E-mailadres</label>
<input id="email" name="email" type="email" required aria-describedby="email-help email-error">
<div id="email-help">We sturen een bevestiging naar dit adres.</div>
<div id="email-error" aria-live="polite"></div>

Stap-voor-stap: server- en client-side validatie met error linking

  1. Valideer server-side en retourneer per veld: foutboodschap + veld-id.
  2. Zet voor elk foutveld aria-invalid=”true” en update aria-describedby naar de foutdiv.
  3. Zorg dat foutdiv role=”alert” of aria-live=”assertive” heeft voor directe aankondiging.
// eenvoudige client-side snippet (van server response)
function applyErrors(errors){ // errors = {email: 'Ongeldig e-mailadres', name: 'Verplicht'}
  Object.keys(errors).forEach(function(id){
    var input = document.getElementById(id);
    var errId = id + '-error';
    var errEl = document.getElementById(errId);
    input.setAttribute('aria-invalid','true');
    input.setAttribute('aria-describedby', (input.getAttribute('aria-describedby') || '') + ' ' + errId);
    errEl.textContent = errors[id];
  });
  // focus op eerste foutveld
  var firstInvalid = document.querySelector('[aria-invalid="true"]');
  if(firstInvalid) firstInvalid.focus();
}

Focus management na submit (concrete implementatie)

// naive focus to first invalid field after submit
form.addEventListener('submit', function(e){
  e.preventDefault();
  // ... validate, get errors ...
  if(hasErrors){
    applyErrors(errors);
    // zet tabindex op -1 voor focusable container indien nodig
    var firstInvalid = document.querySelector('[aria-invalid="true"]');
    firstInvalid.setAttribute('tabindex','-1'); // tijdelijk
    firstInvalid.focus();
    firstInvalid.removeAttribute('tabindex');
  } else {
    form.submit();
  }
});

Toegankelijke custom controls (voorbeeld toggle)

Gebruik semantic roles, keyboard handlers en reflecteer state via aria-checked/aria-pressed.

<button id="newsletter-toggle" role="switch" aria-checked="false">Nieuwsbrief</button>
<script>
var t = document.getElementById('newsletter-toggle');
t.addEventListener('click', function(){ toggle(t); });
t.addEventListener('keydown', function(e){
  if(e.key===' '||e.key==='Enter'){ e.preventDefault(); toggle(t); }
});
function toggle(el){
  var state = el.getAttribute('aria-checked') === 'true';
  el.setAttribute('aria-checked', !state);
  // update hidden input for form submission
  document.getElementById('newsletter').value = (!state).toString();
}
</script>

Visuele focus en kleurcontrast voor foutstaten

/* CSS: duidelijke focus en foutstijl */
input:focus { outline: 3px solid #005A9C; outline-offset: 2px; }
input[aria-invalid="true"] { border-color: #d13438; box-shadow: 0 0 0 3px rgba(209,52,56,0.15); }
.error-icon { color: #d13438; /* niet alleen kleur gebruiken voor status */ }

Checklist voor developers

  • Labels: elk input element heeft een expliciet gekoppeld <label for> of een wrapper <label>.
  • ARIA: bij fouten aria-invalid=”true” en aria-describedby verwijst naar foutboodschap.
  • Foutmeldingen: gebruik role=”alert” of aria-live voor directe aankondiging.
  • Focus: focus naar het eerste foutveld na submit; houd logische tabindex-volgorde.
  • Keyboard: alle interactieve controls zijn per keyboard bedienbaar (Tab, Enter, Space, Arrow keys waar relevant).
  • Custom controls: correct roles (button, switch, listbox), aria-* attributen en keyboard handlers.
  • Contrast: foutkleuren voldoen aan contrastcriteria en geef ook tekstuele aanwijzingen.
  • Form validation: combineer client-side en server-side en link serverfouten terug naar velden.
  • Testen: handmatige toetsenbord- en screenreadertests, plus automatische checks via onze WCAG checker/validator.

Tips voor designers en redacties

Design tokens en states

Maak design tokens voor focus, error en valid states zodat developers consistente CSS-classnames gebruiken. Beschrijf in het design system wanneer en hoe foutmeldingen getoond worden (inline, boven formulier, toast) en hoe ze gekoppeld worden aan velden.

Copywriting voor foutmeldingen

Maak foutmeldingen kort, actiegericht en specifiek. Gebruik de template: probleem + waarom + hoe op te lossen. Voorbeeld: “Ongeldig e-mailadres — gebruik formaat naam@organisatie.nl”. Zorg dat redacties ook alternatieve tekst en helpteksten onderhouden.

Wireframes: denk in flows, niet alleen schermen

Ontwerp de flow voor foutgevallen: waar komt focus, wat wordt voorgelezen door een screenreader, hoe wordt context behouden bij AJAX-validatie?

Hoe test je dit?

Automatische controles

Draai onze WCAG checker/validator (wcagtool.nl) of installeer de plugin om CI/CD checks op formulieren te automatiseren. De plugin controleert labels, aria-describedby-relaties, role-assignments en contrast. Test direct: gebruik onze checker op je pagina en ontvang concrete issues en code-aanbevelingen.

Handmatige, snelle tests (stap-voor-stap)

  1. Tab-only test: navigeer het formulier met Tab en Shift+Tab. Kun je alle velden bereiken en submitten zonder muis?
  2. Screenreader-test: open NVDA (Windows) of VoiceOver (macOS). Lees formulierlabels en foutmeldingen; voer een fout in en kijk of foutmelding automatisch aangekondigd wordt.
  3. Focus-na-fout: submit met onjuiste data; focus moet naar eerste foutveld springen en error text moeten zichtbaar en geannonceerd zijn.
  4. Color-only: verwijder kleuren in devtools (forced-colors/gray). Zijn status en instructies nog begrijpelijk?

Concrete testcases

  • Veld zonder label: moet worden gemeld door tool en handmatige test.
  • Foutmelding niet gekoppeld: gebruik accessibility tree (browser devtools) om aria-describedby-relatie te verifiëren.
  • Custom control zonder keyboard: test met Space/Enter/Arrows.

Gebruik onze tools

Run je pagina direct op wcagtool.nl of gebruik onze plugin voor je browser of CI-pipeline. Onze validator geeft per issue: WCAG-criterium, urgentie, codevoorbeeld en concrete fix. Vragen? Gebruik ons contactformulier — we antwoorden binnen 24 uur.

Praktische snippets en patterns om direct te plakken

Volledig voorbeeldformulier

<form id="signup" novalidate>
  <label for="name">Naam</label>
  <input id="name" name="name" required aria-describedby="name-help name-error">
  <div id="name-help">Volledige naam zoals op uw ID.</div>
  <div id="name-error" class="error" role="alert" aria-live="assertive"></div>

  <label for="email">E-mail</label>
  <input id="email" name="email" type="email" required aria-describedby="email-error">
  <div id="email-error" class="error" role="alert" aria-live="assertive"></div>

  <button type="submit">Aanmelden</button>
</form>

<script>
document.getElementById('signup').addEventListener('submit', function(e){
  e.preventDefault();
  var data = {name: document.getElementById('name').value, email: document.getElementById('email').value};
  var errors = {};
  if(!data.name) errors.name = 'Naam is verplicht';
  if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) errors.email = 'Ongeldig e-mailadres';
  // clear previous errors
  ['name','email'].forEach(function(id){
    var el = document.getElementById(id);
    el.removeAttribute('aria-invalid');
    var err = document.getElementById(id+'-error');
    err.textContent = '';
  });
  if(Object.keys(errors).length){
    applyErrors(errors); // zie eerder snippet
    return;
  }
  // indien geen fouten: verzend naar server
  // fetch('/api/signup', {method:'POST', body: JSON.stringify(data)})
}); 
</script>

Quick lint-script voor CI (pseudo)

// run in node: zoekt inputs zonder label
const jsdom = require('jsdom');
const {JSDOM} = jsdom;
const fs = require('fs');
const html = fs.readFileSync(process.argv[2], 'utf8');
const dom = new JSDOM(html);
const document = dom.window.document;
const inputs = Array.from(document.querySelectorAll('input, select, textarea'));
const unlabeled = inputs.filter(i => {
  const id = i.id;
  if(id && document.querySelector('label[for="'+id+'"]')) return false;
  if(i.closest('label')) return false;
  return true;
});
if(unlabeled.length){ console.error('Unlabeled fields:', unlabeled.map(n=>n.outerHTML)); process.exit(2); } else { console.log('All labeled'); }

Test nu je eigen formulier met onze validator op wcagtool.nl. Download ook onze plugin voor continue checks tijdens development. Nog hulp nodig? Vul ons contactformulier in — we antwoorden binnen 24 uur met concrete codevoorstellen.

Laatste tip: voeg bij elke foutmelding een unieke id en gebruik aria-describedby om die id aan het betreffende input te koppelen; dit is vaak de snelste fix om een formulier toegankelijk te maken en direct beter te laten werken met screenreaders.

Previous Post Next Post

Geef een reactie

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