TODO: PageHeader

Hoofdmenu

WCAG-succescriterium 3.3.2 Labels of instructies

Niveau A

Een bezoeker die een formulier invult, ziet bij elk veld een label dat beschrijft wat er wordt verwacht.

Bijvoorbeeld: “Voornaam”, “E-mailadres”, “Postcode”. Waar het invoerformaat niet vanzelfsprekend is, staat er een instructie bij: “Gebruik het formaat dd-mm-jjjj” of “Minimaal 8 tekens”. Zo weet de bezoeker wat er moet worden ingevuld voordat er een fout ontstaat.

Labels zijn zichtbaar en blijven zichtbaar terwijl de bezoeker het veld invult. Zodra het veld focus krijgt, leest een screenreader het label voor — of de alt-tekst als een icoon als label dient. Instructies staan bij het veld en niet alleen in een inleidende tekst bovenaan het formulier.

Veelgemaakte fouten

Invoerveld heeft geen zichtbaar label

Elk invoerveld heeft een zichtbaar label dat beschrijft wat er moet worden ingevuld. Een veld zonder label dwingt de bezoeker om uit de context te raden wat er wordt verwacht. Een screenreader leest alleen “invoerveld” voor zonder verdere informatie.

Hoe te testen: bekijk het formulier. Heeft elk veld een zichtbaar label? Klik op het label.

Niet doen

Invoerveld zonder zichtbaar label

() => createVNode("p", { children: createVNode("input", { type: "email", placeholder: "E-mailadres" }) })

Doen

Invoerveld met een zichtbaar en gekoppeld label

() => createVNode("p", { children: [createVNode("label", { htmlFor: "vis-email", children: "E-mailadres" }), createVNode("br", {}), createVNode("input", { type: "email", id: "vis-email" })] })

Wie kan dit oplossen: een developer voegt een <label>-element toe dat via for en id is gekoppeld aan het invoerveld.

Label is alleen beschikbaar voor screenreaders

Een invoerveld heeft een aria-label of een visueel verborgen <label>, maar geen zichtbaar label op het scherm. Een screenreader leest het label voor, maar een ziende bezoeker ziet niet wat er in het veld moet worden ingevuld. Dit succescriterium vereist dat labels zichtbaar zijn voor iedereen, niet alleen voor hulpsoftware.

Hoe te testen: bekijk het formulier zonder hulpsoftware. Heeft elk veld een zichtbaar label? Inspecteer de velden in de DevTools: staat het label in een aria-label of in een element met class="sr-only" of vergelijkbare visueel verborgen CSS?

Niet doen

Label alleen beschikbaar via aria-label

<input type="text" aria-label="Zoekterm" placeholder="Zoeken..." />

Niet doen

Label visueel verborgen met sr-only

<label for="zoek" class="sr-only">
  Zoekterm
</label>
<input type="text" id="zoek" placeholder="Zoeken..." />

Doen

Zichtbaar label voor iedereen

<label for="zoek">Zoekterm</label>
<input type="text" id="zoek" />

Wie kan dit oplossen: een developer vervangt het verborgen label door een zichtbaar label. Een designer maakt ruimte in het ontwerp voor zichtbare labels.

Zoekveld heeft alleen een icoon als label en dat icoon is niet zichtbaar voor iedereen

Een zoekveld zonder tekstlabel kan acceptabel zijn als de context ondubbelzinnig is — bijvoorbeeld een vergrootglasicoon naast een zoekknop. Maar als dat icoon een contrast van minder dan 3:1 heeft met de achtergrond, is het niet zichtbaar voor bezoekers met verminderd gezichtsvermogen. Het veld heeft dan voor die bezoekers geen herkenbare aanduiding.

Hoe te testen: meet het contrast van het vergrootglasicoon ten opzichte van de achtergrond. Is het lager dan 3:1? Dan is het icoon niet voor iedereen zichtbaar en functioneert het niet als label.

Niet doen

Zoekveld met een vergrootglasicoon dat onvoldoende contrast heeft

() => createVNode("p", { style: { display: "flex", alignItems: "center", gap: "4px" }, children: [createVNode("input", { type: "search", "aria-label": "Zoeken", placeholder: "Zoeken..." }), createVNode("button", { style: { background: "none", border: "none", padding: "4px" }, children: createVNode("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "#CCCCCC", "aria-hidden": "true", children: createVNode("path", { d: "M15.5 14h-.79l-.28-.27A6.47 6.47 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" }) }) })] })

Doen

Zoekveld met een zichtbaar tekstlabel

() => createVNode("p", { style: { display: "flex", alignItems: "center", gap: "4px" }, children: [createVNode("label", { htmlFor: "vis-zoek", children: "Zoeken" }), createVNode("input", { type: "search", id: "vis-zoek" }), createVNode("button", { style: { background: "none", border: "none", padding: "4px" }, children: createVNode("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "#333333", "aria-hidden": "true", children: createVNode("path", { d: "M15.5 14h-.79l-.28-.27A6.47 6.47 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" }) }) })] })

Doen

Zoekveld met een zoekknop die tekst bevat

() => createVNode("p", { style: { display: "flex", alignItems: "center", gap: "4px" }, children: [createVNode("input", { type: "search", "aria-label": "Zoeken" }), createVNode("button", { children: "Zoeken" })] })

Wie kan dit oplossen: een developer voegt een zichtbaar tekstlabel toe, of een zoekknop met zichtbare tekst. Een designer zorgt dat het icoon voldoende contrast heeft als het de enige visuele aanduiding is.

Placeholder wordt gebruikt als label

Een placeholder verdwijnt zodra de bezoeker begint te typen. De bezoeker weet dan niet meer wat er in het veld moet worden ingevuld. Placeholders zijn niet bedoeld als vervanging van een label.

Hoe te testen: begin met typen in een veld dat alleen een placeholder heeft. Verdwijnt de placeholder zodra je begint te typen?

Niet doen

Placeholder als enige aanduiding van het veld

() => createVNode(Fragment, { children: [createVNode("p", { children: createVNode("input", { type: "text", placeholder: "Voornaam" }) }), createVNode("p", { children: createVNode("input", { type: "text", placeholder: "Achternaam" }) }), createVNode("p", { children: createVNode("input", { type: "email", placeholder: "E-mailadres" }) })] })

Doen

Zichtbaar label met een placeholder als aanvullend voorbeeld

() => createVNode(Fragment, { children: [createVNode("p", { children: [createVNode("label", { htmlFor: "vis-voornaam", children: "Voornaam" }), createVNode("br", {}), createVNode("input", { type: "text", id: "vis-voornaam", placeholder: "bijv. Fatima" })] }), createVNode("p", { children: [createVNode("label", { htmlFor: "vis-achternaam", children: "Achternaam" }), createVNode("br", {}), createVNode("input", { type: "text", id: "vis-achternaam", placeholder: "bijv. De Vries" })] }), createVNode("p", { children: [createVNode("label", { htmlFor: "vis-email2", children: "E-mailadres" }), createVNode("br", {}), createVNode("input", { type: "email", id: "vis-email2", placeholder: "bijv. naam@voorbeeld.nl" })] })] })

Wie kan dit oplossen: een developer voegt permanent zichtbare labels toe aan de velden. De placeholders kunnen blijven staan als aanvullend voorbeeld.

Verwacht invoerformaat ontbreekt

Een veld verwacht een specifiek formaat, maar dat staat nergens vermeld. Een datumveld zonder instructie laat de bezoeker raden: is het dd-mm-jjjj of mm/dd/yyyy? Een telefoonnummerveld zonder instructie: met of zonder landcode? Met spaties of aaneengesloten?

Hoe te testen: bekijk elk veld dat een specifiek formaat verwacht. Staat het verwachte formaat bij het veld, als placeholder of als instructietekst?

Niet doen

Datumveld zonder formaatinstructie

<label for="geboortedatum">Geboortedatum</label>
<input type="text" id="geboortedatum" />

Doen

Datumveld met formaatinstructie

<label for="geboortedatum">Geboortedatum</label>
<input type="text" id="geboortedatum" aria-describedby="datum-hint" placeholder="dd-mm-jjjj" />
<p id="datum-hint" class="hint">
  Gebruik het formaat dd-mm-jjjj, bijvoorbeeld 15-03-1990
</p>

De instructie is via aria-describedby gekoppeld aan het veld, zodat een screenreader het voorleest wanneer het veld focus krijgt.

Wie kan dit oplossen: een developer voegt een instructie toe bij het veld en koppelt die via aria-describedby.

Verplichte velden zijn niet gemarkeerd

Een formulier heeft verplichte en optionele velden, maar er is geen aanduiding welke verplicht zijn. De bezoeker ontdekt het pas na het verzenden. Markeer verplichte velden met een zichtbare aanduiding en het required-attribuut of aria-required="true".

Hoe te testen: bekijk het formulier. Kun je voor het invullen zien welke velden verplicht zijn? Is er een legenda die het sterretje of de aanduiding “(verplicht)” verklaart?

Niet doen

Verplicht veld zonder aanduiding

() => createVNode("p", { children: [createVNode("label", { htmlFor: "vis-naam1", children: "Naam" }), createVNode("br", {}), createVNode("input", { type: "text", id: "vis-naam1" })] })

Doen

Verplicht veld met zichtbare aanduiding en required-attribuut

() => createVNode("p", { children: [createVNode("label", { htmlFor: "vis-naam2", children: ["Naam ", createVNode("span", { "aria-hidden": "true", children: "*" })] }), createVNode("br", {}), createVNode("input", { type: "text", id: "vis-naam2", required: true })] })

Doen

Verplicht veld met het woord 'verplicht' in het label

() => createVNode("p", { children: [createVNode("label", { htmlFor: "vis-naam3", children: "Naam (verplicht)" }), createVNode("br", {}), createVNode("input", { type: "text", id: "vis-naam3", required: true })] })

Als de meeste velden verplicht zijn, markeer dan de optionele velden met “(optioneel)” in plaats van alle verplichte velden met een sterretje.

Wie kan dit oplossen: een developer voegt de markering toe aan de labels en het required-attribuut aan de velden.

Instructie staat alleen bovenaan het formulier

Een formulier begint met een blok instructietekst dat uitlegt hoe het formulier moet worden ingevuld. Verder in het formulier ontbreken instructies bij de individuele velden. Bij een lang formulier is de bezoeker de instructie vergeten tegen de tijd dat het betreffende veld in beeld komt.

Hoe te testen: scroll door een lang formulier. Zijn de instructies beschikbaar bij de velden waar ze relevant zijn, of moet je terugscrollen naar het begin?

Niet doen

Instructies alleen bovenaan het formulier

<p>Vul alle datums in als dd-mm-jjjj. Telefoonnummers zonder spaties.</p>

<!-- Verderop in het formulier, zonder herhaling: -->
<label for="startdatum">Startdatum</label>
<input type="text" id="startdatum" />

<label for="telefoon">Telefoonnummer</label>
<input type="tel" id="telefoon" />

Doen

Instructies bij de individuele velden

<label for="startdatum">Startdatum</label>
<input type="text" id="startdatum" aria-describedby="startdatum-hint" />
<p id="startdatum-hint" class="hint">Gebruik het formaat dd-mm-jjjj</p>

<label for="telefoon">Telefoonnummer</label>
<input type="tel" id="telefoon" aria-describedby="telefoon-hint" />
<p id="telefoon-hint" class="hint">10 cijfers, zonder spaties</p>

Wie kan dit oplossen: een developer plaatst de instructies bij de velden en koppelt ze via aria-describedby.

Groep invoervelden zonder groepslabel

Een formulier heeft een sectie met adresvelden: straat, huisnummer, postcode, plaats. Visueel is duidelijk dat ze bij elkaar horen, maar in de code ontbreekt een groepslabel. Een screenreader leest de velden los van elkaar voor zonder de context van de groep.

Dit komt ook voor bij betalingsformulieren (kaartnummer, vervaldatum, CVC) en bij persoonlijke gegevens (voornaam, achternaam, geboortedatum).

Hoe te testen: ga met een screenreader door een groep gerelateerde velden. Wordt de groepsnaam voorgelezen bij elk veld?

Niet doen

Adresvelden zonder groepering

() => createVNode(Fragment, { children: [createVNode("p", { children: [createVNode("label", { htmlFor: "vis-straat1", children: "Straat" }), createVNode("br", {}), createVNode("input", { type: "text", id: "vis-straat1" })] }), createVNode("p", { children: [createVNode("label", { htmlFor: "vis-huisnummer1", children: "Huisnummer" }), createVNode("br", {}), createVNode("input", { type: "text", id: "vis-huisnummer1" })] }), createVNode("p", { children: [createVNode("label", { htmlFor: "vis-postcode1", children: "Postcode" }), createVNode("br", {}), createVNode("input", { type: "text", id: "vis-postcode1" })] })] })

Doen

Adresvelden gegroepeerd met fieldset en legend

() => createVNode("fieldset", { children: [createVNode("legend", { children: "Factuuradres" }), createVNode("p", { children: [createVNode("label", { htmlFor: "vis-straat2", children: "Straat" }), createVNode("br", {}), createVNode("input", { type: "text", id: "vis-straat2" })] }), createVNode("p", { children: [createVNode("label", { htmlFor: "vis-huisnummer2", children: "Huisnummer" }), createVNode("br", {}), createVNode("input", { type: "text", id: "vis-huisnummer2" })] }), createVNode("p", { children: [createVNode("label", { htmlFor: "vis-postcode2", children: "Postcode" }), createVNode("br", {}), createVNode("input", { type: "text", id: "vis-postcode2" })] })] })

Wie kan dit oplossen: een developer groepeert de velden met <fieldset> en <legend>.

Hoe te testen

Voor iedereen

  1. Bekijk elk invoerveld in het formulier. Heeft elk veld een zichtbaar label dat beschrijft wat er wordt verwacht?
  2. Controleer of verplichte velden zijn gemarkeerd. Weet je voor het invullen welke velden verplicht zijn?
  3. Controleer velden die een specifiek formaat verwachten. Staat het verwachte formaat bij het veld?
  4. Begin te typen in een veld. Verdwijnt het label? Zo ja, weet je nog wat er wordt verwacht?

Voor developers

  1. Inspecteer elk invoerveld in de DevTools. Heeft elk veld een gekoppeld <label> via for en id? Zoek naar <input>, <select> en <textarea> zonder gekoppeld label.
  2. Controleer of instructies via aria-describedby zijn gekoppeld aan het bijbehorende veld. Navigeer met een screenreader naar het veld en controleer of de instructie wordt voorgelezen.
  3. Controleer of verplichte velden het required-attribuut of aria-required="true" hebben.
  4. Controleer groepen gerelateerde velden. Zijn ze gegroepeerd met <fieldset> en <legend>?
  5. Test labels die in het veld zweven (floating labels) bij 200% en 400% zoom. Blijven ze zichtbaar en leesbaar?
  6. Gebruik axe DevTools of WAVE voor een eerste scan. Ontbrekende labels worden automatisch herkend. Of de labels inhoudelijk duidelijk zijn, controleer je handmatig.

Gerelateerde succescriteria

Gerelateerde NL Design System-richtlijnen

Relevante bronnen

Gebruikersonderzoek

Heb je gebruikersonderzoek gedaan dat betrekking heeft op dit succescriterium en wil je dit delen? Kijk eens bij Gebruikersonderzoeken delen op gebruikersonderzoeken.nl.

W3C referenties

Belangrijk: De richtlijnen van NL Design System zijn geen wettelijke verplichting

De richtlijnen van NL Design System zijn niet wettelijk verplicht en zijn geen vervanging voor de wettelijk geldende WCAG 2.1 specificatie.

Ons doel is om praktische uitleg en voorbeelden te geven die helpen bij het toegankelijk inzetten van de NL Design System componenten, patronen en richtlijnen. We doen dat op basis van een interpretatie van de nieuwe WCAG 2.2 specificatie.

Weten waar je volgens de wet aan moet voldoen? Ga dan naar wat is verplicht van DigiToegankelijk.

Help richtlijn verbeteren

Aanvullingen of opmerkingen?

Deze pagina’s over WCAG worden onderhouden door NL Design System. Heb je aanvullingen of opmerkingen? Deel je mening op GitHub.