Stel je voor: je vult een formulier in, klikt op verzenden en ziet groen oplichten: “Bedankt, je bericht is verzonden.” Maar gebruik je een schermlezer, dan hoor je niks. Kreeg het systeem je bericht eigenlijk wel?
Statusberichten zijn de kleine meldingen die je overal op websites ziet: een bevestiging na een formulier, een foutmelding bij een verkeerd ingevuld veld, een teller die meegroeit als je een filter aanzet. In dit artikel leg ik uit hoe je deze meldingen ook beschikbaar maakt voor schermlezers. Statusberichten vallen onder succescriterium 4.1.3 Statusberichten (niveau AA).
Wat zegt WCAG 4.1.3 eigenlijk?
Succescriterium 4.1.3 Statusberichten valt onder het principe “Robuust” en stelt:
In content die is geïmplementeerd met opmaaktalen kunnen statusberichten door software bepaald worden met behulp van rol (role) of eigenschappen (properties), zodat hulptechnologieën de berichten aan de gebruiker kunnen presenteren zonder dat ze de focus krijgen.
Simpel gezegd: laat je een melding op het scherm verschijnen over een actie of gebeurtenis? Dan moet die melding ook beschikbaar zijn voor hulptechnologieën zoals een schermlezer. En dat moet kunnen zonder dat de focus naar de melding springt.
Dit succescriterium verplicht je niet om statusberichten te gebruiken. Het zegt alleen: als je ze gebruikt, dan moeten ze toegankelijk zijn.
Een statusbericht geeft informatie over een van deze vier dingen:
- het succes of resultaat van een actie (“Verzoek verstuurd”, “43 resultaten gevonden”)
- de wachttoestand van een applicatie (“Laden…”)
- de voortgang van een proces (“Bestand uploaden: 45%”)
- fouten (“5 fouten op de pagina”)
En het verschijnt zonder dat de focus verspringt en zonder dat de pagina herlaadt.
Wat is geen statusbericht?
Niet elke verandering op een pagina is een statusbericht. Een bericht is géén statusbericht als:
- de focus naar het bericht verplaatst wordt, bijvoorbeeld in een dialoogvenster. Dat is een contextwijziging en die kondigt de schermlezer al aan.
- er een nieuwe pagina laadt. Herlaadt de pagina na het verzenden van een formulier? Dan is de melding op de nieuwe pagina gewone tekst.
- content uitklapt of inklapt bij een bedieningselement, zoals een accordeon, menu, keuzelijst of tablijst. De toestandswijziging (“uitgevouwen” of “samengevouwen”) valt onder succescriterium 4.1.2 Naam, rol, waarde.
- er nieuwe bedieningselementen verschijnen, zoals extra formuliervelden na een gegeven antwoord.
Ook de content die na een actie verschijnt is zelf geen statusbericht. Een lijst met zoekresultaten valt er niet onder. De melding “5 resultaten gevonden” boven die lijst mogelijk wél.
Andersom geldt: een status die alleen uit een icoon of geluid bestaat is wél een statusbericht. Denk aan een draaiend laadicoon zonder tekst. Je hebt dan twee dingen nodig: een tekstalternatief voor het icoon (succescriterium 1.1.1 Niet-tekstuele content) en een passende rol of eigenschap. De schermlezer kondigt dan bijvoorbeeld “bezig met laden” aan.
Wie heeft baat bij toegankelijke statusberichten?
Toegankelijke statusberichten zijn belangrijk voor deze mensen:
- Mensen die een schermlezer gebruiken. Zij krijgen alleen mee wat de schermlezer aankondigt. Een rood randje, een groen vinkje of tekst die ergens op de pagina verschijnt zonder dat de schermlezer het oppakt, die missen ze helemaal.
- Mensen met een cognitieve beperking. Hulptechnologie kan gemarkeerde statusberichten uitstellen, onderdrukken of juist benadrukken, afhankelijk van wat de gebruiker prettig vindt. Dat kan alleen als het bericht een rol of eigenschap heeft.
Voor ziende gebruikers zonder hulptechnologie is een zichtbare melding meestal duidelijk genoeg. Zij zien de melding meteen in beeld verschijnen.
Hoe schermlezers statusberichten oppakken
Schermlezers zien niet zomaar dat er ergens op de pagina iets verandert. Ze hebben een seintje nodig. Dat seintje geef je met een live region: een stukje HTML met een ARIA-rol of aria-live-attribuut dat zegt “houd dit deel van de pagina in de gaten en lees veranderingen voor”.
Hierbij geldt één regel: plaats de live region al in de HTML voordat het bericht erin komt. Zet je het element pas in de pagina op het moment dat de melding verschijnt? Dan kunnen sommige schermlezers de verandering missen. De live region is er dan wel, maar de schermlezer heeft niet meegekregen dat hij iets in de gaten moest houden.
Zet bij het laden van de pagina dus alvast een leeg containerelement klaar. Vul die container met JavaScript op het moment dat je iets wilt melden.
<!-- Al aanwezig bij het laden van de pagina -->
<div id="formulier-status" role="status" aria-atomic="true"></div>
// Pas later, bijvoorbeeld na een succesvolle verzending
document.getElementById('formulier-status').textContent =
'Je bericht is verzonden.';
Verberg de container niet met display:none of het hidden-attribuut zolang je wacht op een bericht. Een element dat zo verborgen is, staat niet in de accessibility tree. Wil je de container visueel verbergen tot er een bericht is? Plaats hem dan met CSS buiten beeld. Het element is dan wel aanwezig maar niet zichtbaar.
Een schermlezer kondigt alleen aan als de inhoud van de live region echt verandert. Zet je twee keer achter elkaar precies dezelfde tekst neer (bijvoorbeeld dezelfde foutmelding na een tweede poging)? Dan wordt dit vaak niet aangekondigd. Maak in dat geval de region eerst leeg en plaats de tekst er daarna opnieuw in.
// Container leeg maken
const status = document.getElementById('formulier-status');
status.textContent = '';
// Korte vertraging zodat de schermlezer de wijziging echt oppakt
requestAnimationFrame(() => {
status.textContent = 'Vul een geldig e-mailadres in.';
});
Rollen en attributen voor statusberichten
Voor statusberichten gebruik je drie ARIA-rollen:
| Rol | Wanneer je het gebruikt | Impliciete aria-live |
|---|---|---|
role="status" | Bevestiging, teller of resultaat van een actie | polite |
role="alert" | Foutmelding of dringende waarschuwing | assertive |
role="log" | Updates die stap voor stap binnenkomen, zoals een chat | polite |
Elke rol heeft een impliciete aria-live-waarde. role="status" en role="log" werken als polite: de schermlezer wacht tot de huidige aankondiging klaar is. role="alert" werkt als assertive: de schermlezer onderbreekt direct. Je hoeft aria-live dus niet apart toe te voegen bij deze rollen. Sterker nog: een rol en aria-live samen op hetzelfde element kan in sommige schermlezers tot een dubbele aankondiging leiden.
Er bestaat ook een derde waarde: aria-live="off". Dat is de standaardwaarde en betekent: kondig wijzigingen niet aan. Een schermlezer leest de content dan pas voor als de gebruiker er zelf naartoe navigeert. Dit gebruik je bijvoorbeeld voor een ticker met sportuitslagen die constant bijwerkt en die de gebruiker kan pauzeren.
Wil je liever geen aparte rol gebruiken? Gebruik dan gewoon het aria-live-attribuut met de waarde polite of assertive. Dat is handig als je een statusbericht plaatst op een element dat al een andere rol heeft.
Een bekend voorbeeld is een tabel:
<table aria-live="polite">
...
</table>
Een <table>-element heeft impliciet role="table". Schermlezers gebruiken die rol om de tabel als tabel aan te kondigen en om rijen en kolommen te kunnen navigeren. Zou je hier role="status" op zetten, dan ben je die tabelrol kwijt.
Verschillende soorten statusberichten
Hieronder zie je per soort statusbericht welke rol of aanpak past.
Succes, resultaat of wachttoestand met role="status"
Voor meldingen die informatie geven zonder dat je de gebruiker hoeft onderbreken, gebruik je role="status". Denk aan een bevestiging na een formulier of een melding “5 resultaten gevonden” boven een zoekresultatenlijst. De schermlezer kondigt de melding dan aan als het even pauzeert.
<div id="zoek-status" role="status" aria-atomic="true">
5 resultaten gevonden.
</div>
Een wachttoestand meld je op dezelfde manier: zet bijvoorbeeld “Bezig met laden…” in een element metrole="status". Is het laden klaar? Vervang de tekst dan door bijvoorbeeld “Klaar”. Verdwijnt de laadtekst alleen visueel, dan mist iemand die een schermlezer gebruikt dat het laden klaar is.
HTML heeft ook een ingebouwd alternatief: het <output>-element. Dit element heeft impliciet role="status" en werkt automatisch als live region. Het is bedoeld voor het tonen van een resultaat van een berekening of actie.
<form>
<label for="prijs">
Prijs per stuk
</label>
<input type="number" id="prijs" value="20">
<label for="aantal">
Aantal
</label>
<input type="number" id="aantal" value="3">
<!-- Wordt met JavaScript bijgewerkt -->
<output id="totaal" for="prijs aantal">
€60
</output>
</form>
Het for-attribuut koppelt de <output> aan de invoervelden waarvan de waarde afhangt. Zodra je met JavaScript de inhoud van de <output> aanpast, kondigt de schermlezer de nieuwe waarde aan. Maar (nog) niet alle schermlezers doen dit betrouwbaar.
Fouten met role="alert"
Voor dringende meldingen gebruik je role="alert". Een schermlezer onderbreekt dan meteen waar hij mee bezig is en kondigt het bericht aan. Dat is precies wat je wilt bij fouten of urgente waarschuwingen.
<div id="formulier-fouten" role="alert" aria-atomic="true"></div>
const fouten = document.getElementById('formulier-fouten');
fouten.textContent = 'Vul je e-mailadres in.';
Voor inline foutmeldingen in de buurt van een invoerveld koppel je het foutbericht aan het veld via aria-describedby. Het foutbericht zelf geef je ook role="alert" zodat het wordt aangekondigd op het moment dat het verschijnt.
<label for="email">
E-mailadres
</label>
<input type="email" id="email" aria-describedby="email-fout" aria-invalid="false">
<span id="email-fout" role="alert"></span>
Bij validatie zet je aria-invalid="true" en vul je de span met het foutbericht:
const emailVeld = document.getElementById('email');
const emailFout = document.getElementById('email-fout');
emailVeld.setAttribute('aria-invalid', 'true');
emailFout.textContent = 'Vul een geldig e-mailadres in.';
Het veld is nu in de code gemarkeerd als ongeldig en de foutmelding wordt direct aangekondigd. Mensen die een schermlezer gebruiken en later teruggaan naar het veld horen de foutmelding opnieuw via de aria-describedby-koppeling. Dit patroon vult een eventuele samenvatting bovenaan het formulier aan: de samenvatting geeft het overzicht, de inline melding helpt bij het corrigeren.
Naast aria-describedby bestaat er een specifieker attribuut: aria-errormessage. Dat verwijst naar de foutmelding én geeft de schermlezer door dat het om een fout gaat. De ondersteuning in schermlezers is nog wisselend. aria-describedby werkt overal goed, dus dat is voorlopig de veiligste keuze.
Kondig een foutmelding niet aan bij elke toetsaanslag. Een schermlezer kondigt een role="alert"-bericht meteen aan. Iemand die nog aan het typen is wordt constant onderbroken.
Bouw je toch realtime validatie? Voeg dan een korte vertraging in.
Als je het succescriterium tot de letter volgt dan voldoet een foutmelding in een polite-region trouwens ook: het succescriterium eist alleen dat het bericht een rol of eigenschap heeft. Maar bij een echte fout past de directe aankondiging van role="alert" beter.
Voortgang met role="progressbar" en aria-live
Een voortgangsindicator maak je met role="progressbar" en de attributen aria-valuemin, aria-valuemax en aria-valuenow. Die attributen geven de waarde door aan hulptechnologie. Daarmee is de voortgang door software te bepalen en voldoe je aan het succescriterium.
Maar er zit een praktisch probleem: role="progressbar" is zelf geen live region. Veel schermlezers kondigen een wijziging van aria-valuenow niet aan. Koppel daarom een losse tekst met aria-live="polite" aan de indicator. De schermlezer leest dan de waarden voor terwijl de voortgang oploopt.
<div role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="40" aria-labelledby="upload-label" aria-describedby="upload-tekst"></div>
<p id="upload-label">
Bestand uploaden
</p>
<p id="upload-tekst" aria-live="polite">
40% voltooid
</p>
Stapsgewijze updates met role="log"
Soms komen updates niet als één melding binnen, maar stuk voor stuk: een chat, een activiteitenlog, een lijst die aangroeit. Voor die situaties gebruik je role="log". Een schermlezer leest dan alleen de nieuwe regels voor, niet steeds het hele venster. Ook een statusupdate binnen zo’n stroom (denk aan “Sander is aan het typen…”) valt hieronder.
<div id="chat-log" role="log">
<p>
Sander: Hoi, ben je er nog?
</p>
</div>
In de praktijk is de ondersteuning van role="log" wisselend. Sommige schermlezers behandelen het niet anders dan een aria-live="polite"-region.
Aankondigingen bijsturen
Het aria-atomic-attribuut bepaalt of een schermlezer de hele live region voorleest, of alleen het deel dat veranderd is.
Stel je hebt een teller in je winkelwagen die “0 producten” laat zien en daarna “3 producten”. Zonder aria-atomic="true" hoort de gebruiker soms alleen “3” omdat dat het enige is dat veranderd. Mét aria-atomic="true" hoort de gebruiker het hele bericht: “3 producten”.
aria-atomic="true" is ook een impliciete waarde van role="status" en role="alert", maar het werkt in de praktijk niet altijd consistent in alle browsers en schermlezers. Gebruik aria-atomic="true" dus voor de zekerheid bij elk live region die als één geheel moet worden aangekondigd.
Met aria-busy="true" houd je aankondigingen tegen. Zet het op true voordat je meerdere wijzigingen achter elkaar doet, voer dan alle wijzigingen uit en zet het daarna op false. De schermlezer kondigt dan één keer het eindresultaat aan in plaats van elke tussenstap apart.
Met aria-relevant bepaal je welke wijzigingen een schermlezer aankondigt: toevoegingen, verwijderingen of tekstwijzigingen. De standaardwaarde (additions text) klopt bijna altijd. De ondersteuning voor andere waarden is wisselend. Laat dit attribuut dus liever weg.
Toasts en automatisch verdwijnende meldingen
Een toast is een melding die kort verschijnt en daarna vanzelf verdwijnt. Vaak verdwijnt het na een paar seconden vanzelf weer.
Toasts zijn lastig:
- Mensen die een schermlezer gebruiken hebben meer tijd nodig om de melding te horen en te verwerken.
- Mensen die een schermvergroter gebruiken kijken vaak ergens anders op het scherm.
- Mensen met een cognitieve beperking lezen niet altijd zo snel.
Houd daarom rekening met deze drie regels:
- Geef toasts genoeg tijd. Een vuistregel: minimaal 5 seconden, langer als het bericht meer woorden bevat.
- Pauzeer de timer als de gebruiker de melding aanwijst of focust. Dan kan diegene rustig lezen of klikken.
- Zorg dat de informatie ook elders terug te vinden is, bijvoorbeeld in een notificatiecentrum.
Voor een toast met een succesmelding gebruik je role="status". Voor een toast met een foutmelding gebruik je role="alert".
Een toast die verdwijnt is een vorm van tijdslimiet. Succescriterium 2.2.1 Timing aanpasbaar zegt dat de gebruiker zo’n limiet moet kunnen uitzetten, verlengen of aanpassen, tenzij de melding ook ergens anders terug te lezen is (bijvoorbeeld in een notificatiecentrum).
Vereisten en aanbevelingen voor toegankelijke statusberichten
Dit zijn de vereisten en aanbevelingen voor toegankelijke statusberichten.
WCAG-vereisten
- Statusberichten moeten door software te bepalen zijn via een rol of eigenschap, zodat hulptechnologie ze kan aankondigen zonder dat ze de focus krijgen.
- Voor meldingen over succes, resultaat of een wachttoestand is
role="status"(ofaria-live="polite") de gangbare invulling. - Voor foutmeldingen en dringende waarschuwingen is
role="alert"(ofaria-live="assertive") de gangbare invulling. Eenpolite-aankondiging voldoet ook, maar past minder goed bij een dringende melding. - Voor statusberichten die stap voor stap worden bijgewerkt is
role="log"(ofaria-live="polite") de gangbare invulling. - Voor voortgang voldoet
role="progressbar"metaria-valuenowal: de stand is dan via eigenschappen te bepalen.
- Voor meldingen over succes, resultaat of een wachttoestand is
Aanbevolen
- Plaats de container voor statusberichten al in de HTML bij het laden van de pagina, ook als hij nog leeg is.
- Geef een statusbericht altijd tekst in een live region. Een kleur, icoon of randje alleen is voor mensen die een schermlezer gebruiken niet waarneembaar.
- Schrijf korte, concrete meldingen. “Je bericht is verzonden” zegt meer dan “Gelukt!”.
- Schrijf
aria-atomic="true"expliciet bij elke live region zodat het hele bericht wordt voorgelezen. (Doe dit niet bijrole="log": daar wil je alleen de nieuwe regel horen.) - Koppel aan een voortgangsindicator altijd een
aria-live="polite"-bericht voor tussentijdse tekstupdates. Schermlezers kondigen een wijziging vanaria-valuenowvaak niet uit zichzelf aan. - Gebruik het
<output>-element voor live berekeningen of resultaten in een formulier. Dat heeft implicietrole="status". - Geef toasts minimaal 5 seconden de tijd en pauzeer de timer bij focus of hover.
- Beperk het aantal live regions per pagina. Meerdere regions die tegelijk aankondigen, overspoelen de gebruiker.
- Gebruik
role="alert"spaarzaam: alleen voor echt dringende meldingen. - Wacht met foutmeldingen tot de gebruiker een veld verlaat of het formulier verstuurt. Valideer niet bij elke toetsaanslag.
- Test je statusberichten met een echte schermlezer. Zo merk je of de hoeveelheid en de timing van de aankondigingen klopt.
Gerelateerde succescriteria
Dit artikel behandelt succescriterium 4.1.3 Statusberichten (niveau AA) en raakt ook aan 3.3.1 Foutidentificatie, 2.2.1 Timing aanpasbaar, 4.1.2 Naam, rol, waarde en 1.1.1 Niet-tekstuele content.