TODO: PageHeader

Hoofdmenu

WCAG-succescriterium 4.1.3 Statusberichten

Niveau AA

Een bezoeker voegt een product toe aan de winkelwagen en ziet een melding: “Product toegevoegd”. Een formulier wordt verzonden en er verschijnt: “Je bericht is verstuurd”. Die meldingen geven de bezoeker bevestiging dat een actie is gelukt. Een screenreader leest ze automatisch voor, zonder dat de bezoeker er zelf naartoe hoeft te gaan.

Dat werkt alleen als het bericht in de code is gemarkeerd als live region. Een live region is een HTML-element met role="status", role="alert" of aria-live dat de screenreader in de gaten houdt. Zodra de tekst in dat element verandert, leest de screenreader het voor. Zonder die markering verschijnt het bericht wel op het scherm, maar merkt een screenreader er niets van.

Er zijn twee urgentieniveaus. role="status" (of aria-live="polite") wacht tot de screenreader klaar is met de huidige spraak. role="alert" (of aria-live="assertive") onderbreekt de huidige spraak en leest het bericht direct voor.

Veelgemaakte fouten

Bevestigingsmelding na het verzenden van een formulier wordt niet voorgelezen

Een bezoeker verstuurt een formulier en er verschijnt een bevestiging op de pagina. De pagina wordt niet herladen — de melding verschijnt dynamisch. Een screenreader leest de bevestiging automatisch voor zodat de bezoeker weet dat het verzenden is gelukt. Zonder role="status" op het element negeert de screenreader het bericht.

Hoe te testen: verstuur een formulier en luister met een screenreader. Wordt de bevestiging voorgelezen zonder dat je er zelf naartoe gaat?

Niet doen

Bevestigingsmelding zonder ARIA-markering

<p class="bevestiging">Je bericht is verstuurd</p>

Doen

Bevestigingsmelding met role=status

<p class="bevestiging" role="status">
  Je bericht is verstuurd
</p>

Wie kan dit oplossen: een developer voegt role="status" toe aan het element dat de bevestiging bevat.

Urgente waarschuwing wordt niet direct voorgelezen

Een sessie verloopt over twee minuten. Een betaling is mislukt. Een formulier bevat fouten. Dit zijn meldingen die de bezoeker direct moet horen, ook als de screenreader op dat moment iets anders voorleest. role="alert" onderbreekt de huidige spraak. Zonder die markering mist de bezoeker de waarschuwing.

Hoe te testen: veroorzaak een urgente melding. Onderbreekt de screenreader de huidige spraak om het bericht voor te lezen?

Niet doen

Urgente melding zonder ARIA-markering

<p class="waarschuwing">Je sessie verloopt over 2 minuten</p>

Doen

Urgente melding met role=alert

<p class="waarschuwing" role="alert">
  Je sessie verloopt over 2 minuten
</p>

Wie kan dit oplossen: een developer voegt role="alert" toe aan urgente meldingen.

QR-code verloopt zonder melding

Een betaal- of inlogpagina toont een QR-code die na een paar minuten verloopt. Visueel verschijnt er een nieuwe QR-code of een melding dat de vorige is verlopen. Zonder statusbericht merkt een screenreadergebruiker niets: die scant een verlopen code en krijgt een fout die niet te verklaren is.

Hoe te testen: wacht tot een QR-code verloopt. Leest een screenreader voor dat de code is verlopen en dat er een nieuwe beschikbaar is?

Niet doen

QR-code verloopt zonder melding

function vernieuwQRCode() {
  document.getElementById("qr-code").src = genereerNieuweQR();
}

Gebruik hier role="alert" — de bezoeker moet direct weten dat de code is veranderd.

Wie kan dit oplossen: een developer voegt een live region toe die meldt wanneer de QR-code verloopt en wordt vernieuwd.

Nieuwe berichten in een livechat worden niet voorgelezen

Een bezoeker gebruikt een livechat met klantenservice. De medewerker stuurt een antwoord. Het bericht verschijnt in het chatvenster, maar een screenreader meldt niets. De bezoeker wacht op een antwoord dat er al staat.

Hoe te testen: open een livechat en laat iemand anders een bericht sturen. Leest een screenreader het nieuwe bericht voor?

Niet doen

Nieuw chatbericht zonder live region

function ontvangBericht(bericht) {
  const el = document.createElement("div");
  el.className = "chat-bericht";
  el.textContent = bericht.tekst;
  chatContainer.appendChild(el);
}

Doen

Chatcontainer als live region zodat nieuwe berichten worden voorgelezen

<div id="chat-berichten" role="log" aria-live="polite" aria-label="Chatberichten">
</div>
function ontvangBericht(bericht) {
  const el = document.createElement("div");
  el.className = "chat-bericht";
  el.textContent = bericht.afzender + ": " + bericht.tekst;
  document.getElementById("chat-berichten").appendChild(el);
}

role="log" geeft aan dat het om een chronologische reeks berichten gaat. In combinatie met aria-live="polite" leest een screenreader elk nieuw bericht voor zodra de huidige spraak is afgelopen.

Wie kan dit oplossen: een developer voegt role="log" en aria-live="polite" toe aan de chatcontainer.

Update van de winkelwagen wordt niet voorgelezen

Een bezoeker klikt op “Toevoegen aan winkelwagen”. Het aantal in het winkelwagenicoontje verandert van 2 naar 3. Visueel ziet de bezoeker de wijziging. Een screenreader meldt niets — de bezoeker weet niet of de actie is gelukt.

Hoe te testen: voeg een product toe aan de winkelwagen. Leest een screenreader een bevestiging voor?

Niet doen

Winkelwagen-update zonder statusbericht

function voegToeAanWinkelwagen(product) {
  winkelwagen.push(product);
  document.querySelector(".wagen-aantal").textContent = winkelwagen.length;
  document.querySelector(".melding").textContent = "Product toegevoegd";
}

Doen

Winkelwagen-update met een live region

<p class="melding" role="status" aria-live="polite"></p>
function voegToeAanWinkelwagen(product) {
  winkelwagen.push(product);
  document.querySelector(".wagen-aantal").textContent = winkelwagen.length;
  document.querySelector(".melding").textContent =
    product.naam + " is toegevoegd aan je winkelwagen";
}

Het element met role="status" staat al in de HTML voordat het bericht wordt ingevuld. Screenreaders registreren de live region bij het laden van de pagina en luisteren naar wijzigingen.

Wie kan dit oplossen: een developer voegt een live region toe en vult die bij elke winkelwagenactie met een beschrijvend bericht.

Melding over het aantal zoekresultaten wordt niet voorgelezen

Een bezoeker typt in een zoekveld en de resultaten verschijnen direct op de pagina. Er staat een tekst “12 resultaten gevonden” die een screenreader niet voorleest. De bezoeker weet niet dat er resultaten zijn verschenen, laat staan hoeveel.

Hoe te testen: gebruik een zoekveld dat resultaten toont zonder de pagina te herladen. Leest een screenreader het aantal resultaten voor?

Niet doen

Zoekresultaten zonder statusbericht

function toonResultaten(resultaten) {
  container.innerHTML = resultaten.map(r => `<li>${r.titel}</li>`).join("");
}

Doen

Zoekresultaten met een statusbericht over het aantal

<p id="zoek-status" role="status" aria-live="polite"></p>
<ul id="resultaten"></ul>
function toonResultaten(resultaten) {
  container.innerHTML = resultaten.map(r => `<li>${r.titel}</li>`).join("");
  document.getElementById("zoek-status").textContent =
    resultaten.length + " resultaten gevonden";
}

Wie kan dit oplossen: een developer voegt een live region toe die het aantal resultaten meldt.

Filterresultaten in een webshop worden niet gemeld

Een bezoeker selecteert een filter — “Maat M” of “Kleur: blauw” — en de productlijst wordt bijgewerkt. Er verschijnt een bericht: “X resultaten voldoen aan je criteria”. Een screenreader leest dit bericht niet voor. De bezoeker weet niet of het filter heeft gewerkt en hoeveel producten er over zijn.

Hoe te testen: selecteer een filter en luister met een screenreader. Wordt het aantal overgebleven producten voorgelezen?

Niet doen

Filterresultaten bijwerken zonder statusbericht

function pasFilterToe(filter) {
  const resultaten = producten.filter(p => p.kleur === filter);
  toonProducten(resultaten);
}

Doen

Filterresultaten met een statusbericht

<p id="filter-status" role="status" aria-live="polite"></p>
<ul id="producten"></ul>
function pasFilterToe(filter) {
  const resultaten = producten.filter(p => p.kleur === filter);
  toonProducten(resultaten);
  document.getElementById("filter-status").textContent =
    resultaten.length + " producten gevonden";
}

Bij meerdere filters tegelijk vermeld je de actieve filters in het statusbericht, bijvoorbeeld “8 producten gevonden voor Maat M en Kleur: blauw”.

Wie kan dit oplossen: een developer voegt een live region toe die het aantal resultaten meldt bij elke filterwijziging.

Laadstatus is alleen een draaiend icoon

Een bezoeker klikt op een knop en er verschijnt een draaiend icoontje. Visueel begrijpt de bezoeker dat er iets wordt geladen. Een screenreader meldt niets — de bezoeker weet niet of de actie is gestart, of er iets wordt geladen of dat er iets is misgegaan. Hetzelfde geldt voor een tekst “Aan het laden…” die niet als statusbericht is gemarkeerd.

Hoe te testen: klik op een knop die content laadt en luister met een screenreader. Wordt de laadstatus voorgelezen? Wordt het einde van het laden gemeld?

Niet doen

Laadanimatie zonder statusbericht

<button onclick="laadData()">Zoek beschikbaarheid</button>
<div class="spinner" style="display: none;">
  <svg class="draaiend-icoon"><!-- animatie --></svg>
</div>

Doen

Laadstatus met een live region die begin en einde meldt

<button onclick="laadData()">Zoek beschikbaarheid</button>
<div class="spinner" style="display: none;" aria-hidden="true">
  <svg class="draaiend-icoon"><!-- animatie --></svg>
</div>
<p id="laad-status" role="status" aria-live="polite"></p>
function laadData() {
  document.querySelector(".spinner").style.display = "block";
  document.getElementById("laad-status").textContent = "Beschikbaarheid wordt geladen";

  fetch("/api/beschikbaarheid")
    .then(response => response.json())
    .then(data => {
      document.querySelector(".spinner").style.display = "none";
      toonResultaten(data);
      document.getElementById("laad-status").textContent = "Beschikbaarheid geladen";
    });
}

Het draaiende icoon krijgt aria-hidden="true" — het is puur visueel. De live region meldt zowel het begin als het einde van het laden.

Wie kan dit oplossen: een developer voegt een live region toe die de laadstatus in tekst meldt en verbergt het visuele icoon voor screenreaders.

Voortgangsindicator is alleen visueel

Een bezoeker uploadt een bestand en een voortgangsbalk vult zich. Visueel is duidelijk hoever het proces is. Een screenreader meldt niets — de bezoeker weet niet of de upload loopt, hoever die is of wanneer die klaar is.

Hoe te testen: start een upload of een ander proces met een voortgangsbalk. Meldt een screenreader de voortgang?

Niet doen

Voortgangsbalk die alleen visueel is

<div class="voortgang">
  <div class="balk" style="width: 60%"></div>
</div>

Doen

Voortgangsbalk met een progressbar-rol en een statusbericht

<div role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" aria-label="Bestand uploaden">
  <div class="balk" style="width: 60%"></div>
</div>
<p role="status" aria-live="polite">
  60% geüpload
</p>

Wie kan dit oplossen: een developer voegt role="progressbar" toe met de juiste ARIA-attributen en een live region die de voortgang in tekst meldt.

Tekenteller leest bij elke toetsaanslag het resterende aantal voor

Een tekstvak heeft een maximumaantal tekens. Onder het veld staat een teller: “147 van 150 tekens”. Die teller staat in een live region en wordt bij elke toetsaanslag voorgelezen. Bij elk karakter dat de bezoeker typt, onderbreekt de screenreader: “146 van 150 tekens”, “145 van 150 tekens”, “144 van 150 tekens”. Typen wordt onmogelijk.

Een live region die te vaak wordt bijgewerkt is net zo problematisch als een ontbrekende live region. Meld de teller alleen wanneer de bezoeker de limiet nadert.

Hoe te testen: typ in een tekstvak met een tekenteller en luister met een screenreader. Wordt het aantal bij elke toetsaanslag voorgelezen?

Niet doen

Tekenteller als live region die bij elke toetsaanslag wordt bijgewerkt

<textarea id="bericht" maxlength="150"></textarea>
<p id="teller" role="status" aria-live="polite">0 van 150 tekens</p>
bericht.addEventListener("input", function () {
  document.getElementById("teller").textContent =
    this.value.length + " van 150 tekens";
});

Doen

Tekenteller die alleen meldt wanneer de limiet nadert

<textarea id="bericht" maxlength="150" aria-describedby="teller"></textarea>
<p id="teller">0 van 150 tekens</p>
<p id="teller-waarschuwing" role="status" aria-live="polite"></p>
bericht.addEventListener("input", function () {
  const resterend = 150 - this.value.length;
  document.getElementById("teller").textContent =
    this.value.length + " van 150 tekens";

  if (resterend === 20) {
    document.getElementById("teller-waarschuwing").textContent =
      "Nog 20 tekens over";
  } else if (resterend === 0) {
    document.getElementById("teller-waarschuwing").textContent =
      "Tekenlimiet bereikt";
  }
});

De teller zelf is gekoppeld aan het tekstvak via aria-describedby — een screenreader leest het aantal voor wanneer het veld focus krijgt. De aparte live region meldt alleen de drempelwaarden.

Wie kan dit oplossen: een developer verplaatst de live region naar een apart element dat alleen bij drempelwaarden wordt gevuld.

Resultaat van een CAPTCHA wordt niet gemeld

Een bezoeker voltooit een CAPTCHA. Visueel verschijnt een groen vinkje of de tekst “Verificatie geslaagd”. Een screenreader meldt niets — de bezoeker weet niet of de CAPTCHA is gelukt en of het formulier nu verstuurd kan worden. Bij een mislukte poging weet de bezoeker niet dat er een nieuwe CAPTCHA nodig is.

Hoe te testen: voltooi een CAPTCHA en luister met een screenreader. Wordt het resultaat voorgelezen? Probeer ook een fout antwoord.

Niet doen

CAPTCHA-resultaat zonder statusbericht

<div class="captcha-resultaat">
  <span class="vinkje">&#10003;</span> Verificatie geslaagd
</div>

Doen

CAPTCHA-resultaat als statusbericht

<div class="captcha-resultaat" role="status">
  <span class="vinkje" aria-hidden="true">
    &#10003;
  </span>{" "}
  Verificatie geslaagd
</div>

Doen

Mislukte CAPTCHA als urgente melding

<div class="captcha-resultaat" role="alert">
  Verificatie mislukt. Los de nieuwe CAPTCHA op om door te gaan.
</div>

Wie kan dit oplossen: een developer voegt role="status" toe aan een geslaagde verificatie en role="alert" aan een mislukte poging.

Toast-notificatie verdwijnt voordat een screenreader het heeft voorgelezen

Een toast-notificatie verschijnt kort in beeld en verdwijnt na een paar seconden. De focus verplaatst niet naar het bericht. Zonder role="status" leest een screenreader het bericht niet voor. Met role="status" maar een te korte zichtbaarheidsduur verdwijnt het bericht voordat de screenreader het heeft voorgelezen.

Hoe te testen: veroorzaak een toast-notificatie. Leest een screenreader het bericht voor voordat het verdwijnt?

Niet doen

Toast die na 2 seconden verdwijnt zonder ARIA-markering

function toonToast(melding) {
  const toast = document.createElement("div");
  toast.className = "toast";
  toast.textContent = melding;
  document.body.appendChild(toast);
  setTimeout(() => toast.remove(), 2000);
}

Doen

Toast met role=status en voldoende tijd

function toonToast(melding) {
  const toast = document.createElement("div");
  toast.className = "toast";
  toast.setAttribute("role", "status");
  toast.textContent = melding;
  document.body.appendChild(toast);
  setTimeout(() => toast.remove(), 8000);
}

Wie kan dit oplossen: een developer voegt role="status" toe en verlengt de zichtbaarheidsduur.

Live region wordt toegevoegd en gevuld tegelijkertijd

Een live region wordt dynamisch aan de pagina toegevoegd en direct gevuld met tekst. Screenreaders registreren de live region pas nadat het element in de DOM staat. Als het element en de tekst tegelijk verschijnen, mist de screenreader het bericht.

Het element met role="status" of aria-live staat al in de HTML bij het laden van de pagina. De tekst wordt later ingevuld via JavaScript.

Hoe te testen: controleer in de DevTools of het element met role="status" of aria-live al in de HTML staat voordat het bericht verschijnt.

Niet doen

Live region en tekst worden tegelijk toegevoegd

function toonMelding(tekst) {
  const melding = document.createElement("p");
  melding.setAttribute("role", "status");
  melding.textContent = tekst;
  document.body.appendChild(melding);
}

Doen

Live region staat al in de HTML, tekst wordt later ingevuld

<!-- Element staat al in de HTML bij het laden van de pagina -->
<p id="melding" role="status"></p>
function toonMelding(tekst) {
  document.getElementById("melding").textContent = tekst;
}

Wie kan dit oplossen: een developer plaatst het live region-element in het HTML-template en vult het dynamisch met JavaScript.

Hoe te testen

Voor iedereen

  1. Voer acties uit die een melding opleveren: verstuur een formulier, voeg een product toe aan de winkelwagen, zoek, upload een bestand. Verschijnt er een zichtbare bevestiging?
  2. Test met een screenreader. Worden de meldingen automatisch voorgelezen zonder dat je er zelf naartoe gaat?

Voor developers

  1. Zoek in de HTML naar elementen die dynamisch verschijnen na een gebruikersactie. Controleer of ze een role="status", role="alert" of aria-live-attribuut hebben.
  2. Controleer of live regions al in de HTML staan bij het laden van de pagina en niet tegelijk met de tekst worden toegevoegd.
  3. Controleer het urgentieniveau: role="status" voor niet-urgente berichten (bevestigingen, zoekresultaten, voortgang), role="alert" voor urgente meldingen (fouten, verlopen sessies, mislukte acties).
  4. Test toast-notificaties met een screenreader. Worden ze voorgelezen voordat ze verdwijnen?
  5. Controleer voortgangsindicatoren. Hebben ze role="progressbar" met de juiste ARIA-attributen?
  6. Controleer tekentellers en andere elementen die frequent worden bijgewerkt. Wordt de live region niet te vaak geactiveerd?
  7. Gebruik axe DevTools voor een eerste scan. De meeste statusberichtproblemen zijn niet automatisch te herkennen en vereisen handmatig testen met een screenreader.

Gerelateerde succescriteria

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.