Toegankelijke formulieren

Formulieren spelen een belangrijke rol op een website: ze zorgen dat je kunt inloggen, bestellen, contact opnemen of solliciteren. Maar voor veel mensen zijn formulieren een frustrerende ervaring. Labels ontbreken, foutmeldingen zijn onduidelijk en het is niet altijd duidelijk wat je moet invullen.

Dit artikel laat je zien hoe het anders kan.

Duidelijke en beschrijvende labels

Wat maakt een label beschrijvend?

Succescriterium 3.3.2 Labels of instructies schrijft voor dat elk invoerveld een label of instructie heeft. Succescriterium 2.4.6 Koppen en labels eist dat die labels ook beschrijvend zijn. Gebruikers moeten in één oogopslag begrijpen wat er wordt verwacht. Een label zoals “Veld 1” of “Invoer” is te vaag. “E-mailadres” of “Achternaam” is wél beschrijvend.

Een kort label is prima, zolang het maar duidelijk is. “Datum” is bijvoorbeeld voldoende als er maar 1 invoerveld is. Splits je de invoer op in meerdere onderdelen (zoals een datum in dag, maand en jaar)? Geef elk onderdeel dan een eigen zichtbaar label.

Labels koppelen aan het bijbehorende veld

Gebruik een <label>-element voor het label. Koppel elk label aan een invoerveld met het for-attribuut.

<label for="naam">
  Naam:
</label>
<input type="text" id="naam" name="naam">

Een label moet zichtbaar zijn voor alle gebruikers. Alleen een placeholdertekst is daarom niet genoeg, omdat die verdwijnt zodra je begint met typen, vaak te weinig contrast heeft en soms wordt verward met al ingevulde gegevens.

Let op: Een verborgen label via aria-label of een sr-only-class geeft wel een toegankelijke naam aan het invoerveld, maar is niet zichtbaar. Een schermlezer kondigt dit verborgen label wél aan, maar mensen die de pagina bekijken missen de context.

Laat het zichtbare label en de toegankelijke naam overeenkomen

Succescriterium 2.5.3 Label in naam eist dat de toegankelijke naam van een veld het zichtbare label bevat. Mensen die spraakbesturing gebruiken, bedienen invoervelden door het zichtbare label uit te spreken. Wijkt de toegankelijke naam af van wat er op het scherm staat? Dan werkt dat niet.

Waar plaats je een label?

Plaats het label op een voorspelbare plek. Bij tekstvelden en keuzelijsten zet je het label links van of boven het veld. Bij selectievakjes en keuzerondjes zet je het label rechts van het veld.

Met een label vergroot je ook direct het klikbare gebied van het invoerveld.

Markeer verplichte velden

Maak duidelijk welke velden verplicht zijn. Ook dit komt voort uit succescriterium 3.3.2 Labels of instructies. Geef dit zichtbaar aan met tekst, een sterretje (asterisk) of een ander symbool. Als je een sterretje of symbool gebruikt, leg dan bovenaan het formulier uit wat het betekent. Zo weet iedereen direct welke velden verplicht zijn.

<label for="email">
  E-mailadres (verplicht)
</label>
<input type="email" id="email" name="email" required>

Gebruik daarnaast het required-attribuut. Dit helpt schermlezers om aan te geven dat een veld verplicht is.

Het required-attribuut activeert ook de ingebouwde browservalidatie. Gebruik je eigen validatie in plaats van browservalidatie? Zet dan novalidate op het <form>-element. Het required-attribuut zorgt dan alsnog dat schermlezers het veld als verplicht aankondigen, zonder dat de browser zelf foutmeldingen toont.

Geef instructies vooraf

Vertel vooraf wat je van de invoer verwacht, niet (alleen) achteraf als het mis gaat. Plaats de instructie in het label of koppel deze aan het invoerveld met het aria-describedby-attribuut.

Bij een geboortedatum kun je het verwachte formaat tonen:

<label for="geboortedatum">
  Geboortedatum:
</label>
<span id="datum-hint">
  Gebruik het formaat DD-MM-JJJJ
</span>
<input type="text" id="geboortedatum" name="geboortedatum" aria-describedby="datum-hint">

Geef bij velden waarbij in een specifiek formaat moet worden ingevuld altijd een voorbeeld. Denk dan aan: “Bijvoorbeeld 15-03-1990” bij een datumveld, of “Bijvoorbeeld 1234 AB” bij een postcodeveld.

Waar mogelijk kun je ook flexibele invoerformaten accepteren. Accepteer bijvoorbeeld zowel “0612345678” als “06-1234-5678” en herformatteer de invoer automatisch. Dit voorkomt onnodige fouten.

Groepeer gerelateerde velden

Gebruik het <fieldset>-element met <legend> om gerelateerde invoervelden te groeperen. Dit is vooral belangrijk bij keuzerondjes en selectievakjes.

<fieldset>
  <legend>
    Kies je pakket:
  </legend>
  <input type="radio" id="basis" name="pakket" value="basis">
  <label for="basis">
    Basis
  </label>
  <input type="radio" id="premium" name="pakket" value="premium">
  <label for="premium">
    Premium
  </label>
</fieldset>

De <legend> beschrijft waar de groep over gaat. Een schermlezer leest dit voor bij elk veld in de groep, zodat gebruikers de context behouden. Houd de legend daarom zo kort mogelijk.

Je hebt niet altijd een <fieldset> nodig. Een losse checkbox zoals “Ik ga akkoord met de voorwaarden” heeft op zichzelf al voldoende context. Gebruik <fieldset> en <legend> alleen als de individuele labels zonder groepslabel niet duidelijk genoeg zijn.

Help browsers met automatisch invullen

Succescriterium 1.3.5 Identificeer het doel van de input eist dat je het doel van invoervelden die persoonlijke informatie van de gebruiker verzamelen in de code aangeeft. Browsers kunnen formulieren automatisch invullen als ze weten wat voor informatie je vraagt. Dit helpt niet alleen mensen met een cognitieve beperking, maar ook iedereen die snel een formulier wil invullen.

Gebruik het autocomplete-attribuut om aan te geven wat voor informatie je verwacht:

<label for="naam">
  Naam:
</label>
<input type="text" id="naam" name="naam" autocomplete="name">

<label for="email">
  E-mailadres:
</label>
<input type="email" id="email" name="email" autocomplete="email">

<label for="tel">
  Telefoonnummer:
</label>
<input type="tel" id="tel" name="telefoon" autocomplete="tel">
Veelgebruikte inputdoelen
  • name: Volledige naam
  • given-name: Voornaam
  • family-name: Achternaam
  • address-line1: Straatnaam + huisnummer
  • postal-code: Postcode
  • address-level2: Woonplaats
  • bday: Geboortedatum
  • email: E-mailadres
  • tel: Telefoonnummer

Bekijk de volledige lijst met inputdoelen

Gebruik autocomplete bij alle velden die persoonlijke informatie van de gebruiker verzamelen. Vraag je om het informatie van iemand anders? Dan hoef je geen autocomplete te gebruiken.

Gebruik bij inlogformulieren autocomplete="username" en autocomplete="current-password". Zo kunnen wachtwoordmanagers de velden herkennen en automatisch invullen.

Fouten afhandelen

Succescriterium 3.3.1 Foutidentificatie schrijft voor dat je bij een invoerfout aangeeft wáár de fout zit en wát er mis is. Als er iets fout gaat, moet de gebruiker weten wáár de fout zit en wát er precies mis is. Beschrijf de fout altijd in tekst. Gebruik nooit alleen kleur, een icoon of een andere visuele aanwijzing om een fout aan te geven.

Foutmeldingen moeten zichtbaar blijven totdat de gebruiker de fout herstelt. Verwijder een melding niet automatisch na een paar seconden.

Ingebouwde browservalidatie

Browsers hebben ingebouwde validatie. Deze meldingen hebben nadelen: ze zijn vaak te vaag (“Vul dit veld in”), er wordt per verzending maar één fout getoond, de meldingen worden niet altijd goed aangekondigd door een schermlezer en ze schalen niet mee bij zoomen.

Schrijf daarom je eigen foutmeldingen. Zo heb je controle over de tekst, de presentatie en de toegankelijkheid. Zet novalidate op het <form>-element om de ingebouwde validatie uit te schakelen terwijl je het required-attribuut behoudt.

Wanneer toon je foutmeldingen?

Er zijn drie mogelijkheden voor het moment van validatie. Elke aanpak heeft voor- en nadelen.

  1. Validatie na verzenden: Dit is de meest gebruikte aanpak. Je toont alle fouten tegelijk, bij voorkeur in een samenvatting bovenaan het formulier én bij de individuele velden. Het nadeel is dat de gebruiker pas feedback krijgt na het verzenden.
  2. Validatie bij het verlaten van een veld: Deze variant toont feedback zodra de gebruiker naar het volgende veld gaat. Het voordeel is snelle feedback. Het nadeel is dat de aankondiging door een schermlezer kan botsen met de aankondiging van het volgende veld.
  3. Validatie tijdens het typen: Dit geeft directe feedback terwijl de gebruiker nog typt. Gebruik hierbij aria-live="polite" zodat de schermlezer de huidige taak niet onderbreekt. Trigger de melding niet bij elke toetsaanslag, maar gebruik een vertraging. Anders wordt het al snel te overweldigend voor schermlezergebruikers.

Identificeer en beschrijf fouten in tekst

Gebruik drie onderdelen om fouten duidelijk te maken:

  1. Een zichtbare foutmelding in tekst
  2. Koppel de foutmelding aan het veld met aria-describedby
  3. Markeer het veld als ongeldig met aria-invalid="true"
<label for="naam">
  Naam:
</label>
<input type="text" id="naam" name="naam" aria-invalid="true" aria-describedby="naam-fout">
<span id="naam-fout">
  Naam is niet ingevuld. Vul je naam in.
</span>

Zet foutmeldingen dicht bij het betreffende veld. Gebruik nooit alleen kleur om fouten aan te geven.

Wees specifiek in foutmeldingen

Schrijf niet “Dit veld is verplicht”, maar benoem het label van het veld in de foutmelding.

<label for="straat">
  Straatnaam:
</label>
<input type="text" id="straat" name="straat" aria-invalid="true" aria-describedby="straat-fout">
<span id="straat-fout">
  ‘Straatnaam’ is niet ingevuld. Het is een verplicht veld. Vul je straatnaam in.
</span>

Een foutmelding beschrijft wat er mis is gegaan op basis van wat de gebruiker heeft ingevoerd. Gebruik deze vuistregel: “Had deze informatie er ook kunnen staan vóórdat de gebruiker iets heeft ingevuld?” Als het antwoord “ja” is, dan is het een eigenlijk instructie en geen foutmelding.

Maak een samenvatting bij meerdere fouten

Help gebruikers door alle fouten bovenaan te tonen. Maak van de foutmeldingen links naar de bijbehorende velden.

<div role="alert">
  <h2>
    Er zijn fouten in het formulier
  </h2>
  <ol>
    <li>
      <a href="#naam">
        Naam is niet ingevuld. Vul je naam in.
      </a>
    </li>
    <li>
      <a href="#email">
        E-mailadres ‘voorbeeld@’ is ongeldig. Gebruik het formaat voorbeeld@domein.nl.
      </a>
    </li>
  </ol>
</div>

Als het formulier opnieuw geladen wordt na verzenden, pas dan ook het <title>-element aan. Vermeld daarin dat er fouten zijn, bijvoorbeeld: <title>Fouten gevonden – Contactformulier – Mijn website</title>. Schermlezergebruikers horen de paginatitel als eerste en weten zo direct dat ze actie moeten ondernemen.

Zet de focus op de foutmelding

Na validatie moeten schermlezergebruikers direct weten dat er fouten zijn. Dat doe je door de focus te verplaatsen.

Bij validatie zonder paginalading (client-side) zet je de focus met JavaScript op de foutensamenvatting of op het eerste invoerveld met een fout. Zonder deze stap merken schermlezergebruikers mogelijk niet dat er fouten zijn verschenen.

Bij validatie met paginalading (server-side) gebruik je een anker in de URL dat verwijst naar de foutensamenvatting. De browser scrollt dan automatisch naar dat element.

Kondig foutmeldingen aan voor schermlezers

Verschijnen foutmeldingen dynamisch op de pagina, zonder dat de pagina opnieuw laadt? (Bijvoorbeeld bij het verlaten van een veld of tijdens het typen.) Dan moeten schermlezers die meldingen automatisch aankondigen. Dit is een vereiste van succescriterium 4.1.3 Statusberichten.

Het role-attribuut met de waarde "alert" zorgt daarvoor. Gebruik role="alert" op de foutsammenvatting die verschijnt na het versturen van het formulier. Dit onderbreekt de schermlezer onmiddellijk. Gebruik dit op de foutensamenvatting die verschijnt na het versturen.

Foutmeldingen in fieldsets

Bij gegroepeerde velden (zoals keuzerondjes) plaats je de foutmelding direct na de <legend>. Koppel de foutmelding aan elk individueel veld met aria-describedby.

<fieldset>
  <legend>
    Kies je betaalmethode (verplicht)
  </legend>
  <span id="betaling-fout">
    Je hebt nog geen betaalmethode gekozen.
  </span>
  <input type="radio" id="ideal" name="betaling" value="ideal" aria-invalid="true" aria-describedby="betaling-fout">
  <label for="ideal">
    iDEAL
  </label>
  <input type="radio" id="creditcard" name="betaling" value="creditcard" aria-invalid="true" aria-describedby="betaling-fout">
  <label for="creditcard">
    Creditcard
  </label>
</fieldset>

Suggesties voor correctie

Het identificeren van een fout is de eerste stap. De volgende stap is de gebruiker helpen om de fout op te lossen. Succescriterium 3.3.3 Foutsuggestie eist dat je, als het systeem een geldige correctie kan bepalen, een suggestie geeft voor het verbeteren van de invoer.

Er is een belangrijk verschil tussen foutidentificatie en foutsuggestie. Een melding als “E-mailadres is ongeldig” identificeert de fout, maar helpt de gebruiker niet verder. Een melding zoals “Het e-mailadres is niet geldig. Controleer of je een @ hebt gebruikt, bijvoorbeeld naam@voorbeeld.nl.” geeft ook een suggestie voor verbetering.

Een suggestie voor verbetering van de invoer kan op verschillende manieren:

  • Als er een verplicht invoerformaat vereist is:
    • Bij een ongeldig e-mailadres: Het e-mailadres is niet geldig. Controleer of je een @ hebt gebruikt, bijvoorbeeld naam@voorbeeld.nl.
    • Bij een verkeerd datumformaat: Vul je geboortedatum in als DD-MM-JJJJ, bijvoorbeeld 01-02-1993
    • Bij een verkeerd postcodeformaat: Een postcode bestaat uit 4 cijfers en 2 letters, bijvoorbeeld 1234 AB
    • Bij een te lang telefoonnummer: Een telefoonnummer bestaat uit maximaal 10 cijfers. Laat de landcode weg, bijvoorbeeld 0612345678.
  • Als invoer binnen een (vooraf bepaald) bereik moet vallen: Voer een aantal tussen 1 en 50 in.
  • Als het systeem een mogelijke correctie kan voorstellen op basis van bekende waarden: Bedoelde je ‘Amsterdam’?

Wanneer géén suggesties geven?

Geef geen suggesties als dit de beveiliging of het doel van de content in gevaar brengt. Bij een verkeerd wachtwoord vertel je niet wat er precies fout is. Bij een toets of examen geef je geen hints over het juiste antwoord.

Suggesties alleen verplicht als het systeem kan bepalen wat een geldige verbetering is. Bij een veld zoals “opmerking” is dat meestal niet mogelijk.

Voorkom onomkeerbare fouten

Sommige formulieren hebben grote gevolgen. Denk aan een bestelling plaatsen, een contract ondertekenen of persoonlijke gegevens verwijderen. Gaat er dan iets mis, dan kun je dat niet zomaar terugdraaien.

Succescriterium 3.3.4 Foutpreventie (wettelijk, financieel, gegevens) eist dat je bij formulieren met grote gevolgen minstens één van deze drie beschermingsmaatregel biedt:

  1. Maak de actie omkeerbaar: Laat de gebruiker een bestelling annuleren of een wijziging ongedaan maken.
  2. Controleer de invoer vooraf: Check op fouten en geef de gebruiker de kans om ze te corrigeren vóórdat de actie wordt uitgevoerd.
  3. Laat de gebruiker bevestigen: Toon een overzichtspagina waar de gebruiker alles kan nakijken voordat het formulier definitief wordt verzonden.

Dit geldt voor formulieren met financiële transacties, wettelijke verplichtingen, het wijzigen of verwijderen van gegevens, en het verzenden van toets- of examenantwoorden.

Voorkom overbodige invoer

Succescriterium 3.3.7 Overbodige invoer eist dat je niet twee keer om dezelfde informatie vraagt binnen hetzelfde proces. Als een gebruiker in een eerdere stap gegevens heeft ingevoerd, vul die dan automatisch in of bied ze aan zodat de gebruiker ze kan selecteren.

Een veelvoorkomend voorbeeld is een bestelformulier met een aflever- en factuuradres. Bied een selectievakje aan met “Factuuradres is hetzelfde als afleveradres”, zodat de gebruiker het adres niet opnieuw hoeft in te typen. Vul eerder ingevoerde gegevens ook opnieuw in als een formulier een fout bevat en opnieuw wordt getoond.

Er zijn drie uitzonderingen:

  1. het opnieuw invoeren is essentieel (zoals het bevestigen van een e-mailadres)
  2. het is nodig voor beveiliging (zoals het opnieuw invoeren van een wachtwoord)
  3. de eerder ingevoerde informatie is niet langer geldig

Let op: De autocomplete-functie van de browser voldoet niet aan deze eis. De website zelf moet de eerder ingevoerde informatie beschikbaar maken.

Bevestig succesvolle verzending

Bevestig niet alleen fouten, maar ook een succesvolle verzending. Toon een duidelijk bericht wanneer het formulier correct is verzonden, bijvoorbeeld “Je bericht is verzonden”. Gebruik role="status" voor dit succesmelding. Dit wordt door schermlezers aangekondigd zonder de gebruiker te onderbreken.

Vereisten en aanbevelingen voor toegankelijke formulieren

Dit zijn de vereisten en aanbevelingen voor het maken van toegankelijke formulieren:

WCAG-vereisten

  • Geef elk invoerveld een zichtbaar label.
  • Koppel elk label in de code aan het bijbehorende invoerveld via het <label>-element met een for-attribuut.
  • Zorg dat het zichtbare label terugkomt in de toegankelijke naam van het veld.
  • Groepeer gerelateerde velden met <fieldset> en <legend>.
  • Gebruik het autocomplete-attribuut bij velden die persoonlijke informatie verzamelen.
  • Geef foutmeldingen in tekst. Benoem welk veld de fout bevat en beschrijf wat er mis is.
  • Koppel foutmeldingen met aria-describedby.
  • Geef suggesties voor het verbeteren van fouten als die bekend zijn.
  • Bied bij formulieren met financiële of wettelijke gevolgen de mogelijkheid om de actie terug te draaien, de invoer te controleren of het resultaat te bevestigen.
  • Kondig dynamische fout- en succesmeldingen aan voor schermlezers met role="alert" of role="status".
  • Vraag niet opnieuw om informatie die de gebruiker al eerder in hetzelfde proces heeft ingevoerd. Vul deze automatisch in of bied ze aan ter selectie.

Aanbevolen

  • Zet foutmeldingen dicht bij het betreffende veld.
  • Geef instructies vooraf in plaats van alleen achteraf.
  • Markeer velden met een fout met aria-invalid="true".
  • Gebruik een foutensamenvatting bovenaan het formulier met links naar de foutvelden.
  • Zet na validatie de focus op de foutensamenvatting of het eerste veld met een fout.
  • Accepteer flexibele invoerformaten waar mogelijk en herformatteer in de code.

Om te onthouden

Toegankelijke formulieren beginnen met de basis: duidelijke labels, heldere instructies en goede foutafhandeling. Met de juiste HTML-attributen help je niet alleen mensen met een beperking, maar maak je formulieren voor iedereen makkelijker te gebruiken.

Begin klein: Controleer eerst of alle velden zichtbare labels hebben. Voeg dan autocomplete toe. Verbeter je foutmeldingen. Stap voor stap maak je je formulieren toegankelijker.

Gerelateerde succescriteria

Dit artikel behandelt de volgende WCAG-succescriteria:

  1. 1.3.5 Identificeer het doel van de input (niveau AA)
  2. 2.4.6 Koppen en labels (niveau AA)
  3. 2.5.3 Label in naam (niveau A)
  4. 3.3.1 Foutidentificatie (niveau A)
  5. 3.3.2 Labels of instructies (niveau A)
  6. 3.3.3 Foutsuggestie (niveau AA)
  7. 3.3.4 Foutpreventie (wettelijk, financieel, gegevens) (niveau AA)
  8. 3.3.7 Overbodige invoer (niveau A)
  9. 4.1.3 Statusberichten (niveau AA)