Saved. Added to basket. Three errors found. These are the small confirmations an interface uses to tell somebody their action worked, and they are drawn as far from where the person was looking as the layout allows, then taken away again after a few seconds.
A sighted user catches that in peripheral vision. A screen reader user hears nothing at all unless the message was marked as something to announce. Somebody working at high magnification is looking at a fraction of the screen and misses a corner toast as completely as if it never rendered.
One thing to settle before the ticket is written. The Authoring Practices Guide covers thirty component patterns and the toast is not one of them. The Alert pattern is the nearest thing in it and it is a different animal, because an alert is a single live region rather than a stack of dismissible messages on a timer. So there is no canonical keyboard model here to copy, and the rules below are the whole of what you are held to.
What the Rule Requires
4.1.3 Status Messages says that when a page tells somebody something without moving focus there, assistive technology has to be able to announce it. Note what it does not say. It does not require you to invent messages. A page that shows no status messages passes it without doing anything, and the whole criterion is about the ones you already display reaching everybody you already display them to.
The definition is narrower than anything that appears. A status message reports the success or result of an action, a waiting state, the progress of a process, or the existence of errors, and it is not a change of context. New form fields appearing on the page are not status messages, and neither is a list of search results. The count above the list is.
Moving focus to the message instead is a different design rather than a forbidden one. It takes the change out of this criterion entirely, because a change of context is not a status message, and it can raise its own problems under the rules about things happening on focus or on input. It is usually the wrong choice for a routine confirmation, because interrupting somebody's task to tell them the thing they expected happened is worse than telling them quietly. Pick one route. Doing both announces it twice.
The live region has to exist on the page before the message goes into it. Insert the container and its text in the same instant and assistive technology tends to announce nothing, because it was not watching a region that did not exist a moment ago. Start with an empty live region, then write into it as a separate step. There is one documented exception, which is role="alert", where content is usually announced even when the region arrives with its message already inside. Treat that as a convenience rather than something to design around, because the two most-read sources hedge it differently and one of them says outright that alerts present before the page finishes loading are not announced at all.
That last point has a practical consequence worth carrying. A server-rendered error summary on a reloaded page, which is the most common implementation of a good pattern, may be silent for exactly that reason.
Polite or Assertive
| Setting | Behavior | Use for |
|---|---|---|
aria-live="polite" | Queues, and waits for a pause in speech | Saved, added, results updated |
aria-live="assertive" | Interrupts whatever is being said | Errors, session expiry, payment failed |
role="status" | A named role that is polite by default, and atomic by default | Most confirmations |
role="alert" | A named role that is assertive by default, and atomic by default | Genuine problems needing attention now |
Five things can carry a status message, and a tester who knows three of them files false findings. The HTML output element, role="status", role="alert", role="log", and aria-live set to polite or assertive. The absence of all five is the documented failure, and role="status" is the published technique for a routine confirmation.
Reach for polite by default. An assertive announcement cuts off whatever the screen reader was in the middle of saying, so it is a real cost, and it should be spent on the errors and the delays that cost the user something. One implementation detail with a name. Combining aria-live and role="alert" on the same element is a common belt-and-braces habit and it causes double speaking in VoiceOver on iOS, so pick one. And in some browser and screen reader pairs an alert is automatically prefixed with the word Alert when it is read, which is worth knowing before you write Alert into the message text as well.
role="log" is the one nobody reaches for and it is the right answer more often than you would think. A log region appends rather than replacing, so it suits anything where entries accumulate and the older ones stay relevant, such as a chat thread or a run of file uploads finishing one after another.
The Attribute Nobody Sets
Politeness decides when a region speaks. aria-atomic decides what it says. Set to true, the whole region is read out when anything inside it changes. Set to false, only the part that changed is read.
That matters more than it sounds. A basket badge going from 2 to 3 inside a sentence announces the word three, and nothing else, because three is the only thing that changed. role="status" carries an implicit atomic value of true and a bare aria-live="polite" container defaults to false, so the two are not interchangeable however similar they look. W3C's own technique says to set aria-atomic="true" explicitly anyway, because some environments do not treat role="status" as atomic by default. One attribute turns three into three items in your basket.
There is a companion attribute worth knowing about, because it decides something the first two do not. aria-relevant controls which kinds of change count, and it defaults to additions and text. Removals are not announced. That is the mechanism behind a real problem the next section is about.
One argument for keeping status text short that has nothing to do with speech. Assistive technology may reserve a few cells of a braille display to render a status region, and a sentence does not fit in a few cells.
A Message Disappearing Is Also Information
This is the case almost nobody handles. A Saving indicator appears, then vanishes when the save completes. For a sighted user the vanishing is the news. For a screen reader user nothing was announced, because removals are not announced by default, so the last thing they heard was Saving and there has been silence ever since.
W3C's answer is to change the visible text rather than remove it, so Saving becomes Saved. Where the design will not allow that, put the words into a visually hidden status region instead, so something says the state ended. The same applies to a maintenance banner that simply disappears when the service comes back.
Toasts and the Clock
Four seconds is a common default in component libraries. It is enough time to glance at a message and not much else, and it is not enough for somebody magnifying the screen who has to find the toast before reading it, or somebody whose screen reader is mid-announcement when it appears, or anybody who needs to read a sentence twice.
Here is the part we had wrong, and it changes what you should build. A dismiss control is not what 2.2.1 Timing Adjustable asks for. That criterion names three routes and a dismiss button is none of them. Turn the limit off before it is encountered. Adjust it, over a range of at least ten times the default, before it is encountered. Or warn the user before it expires, give them at least 20 seconds to extend it with a simple action, and let them extend at least ten times. Pausing on hover is not one of them either, and it cannot be, because a screen reader user is not hovering.
What actually decides conformance is a different question altogether, and W3C works a toast through as its own example. An email client shows a new-mail toast that disappears after five seconds. That conforms, because the arrival of the mail is discoverable in the inbox anyway, so the disappearing message set no time limit on anybody's ability to find out. Then comes the sentence that matters. If the user has no other means of discovering the same information, each message has to meet the criterion in full.
So the test is not how many seconds. It is whether the information exists anywhere else.
- Never put the only copy of important information in something that disappears. A failed payment needs a message that stays. This is the test, not a nicety.
- Where the same news is recoverable elsewhere, a short-lived toast is fine, and a longer duration with a dismiss control is still kinder.
- Where it is not recoverable elsewhere, you owe one of the three routes above, or you remove the timer.
- Think before you stack them. Preserving every toast so a fast sequence does not overwrite itself solves one problem and walks into another, because 2.2.4 Interruptions is about letting somebody postpone or turn off interruptions, and frequent interruption is specifically named as harmful for people with visual and cognitive disabilities. A queue of assertive toasts is the thing that criterion describes.
- At Level AAA the argument is over. 2.2.3 No Timing removes time limits from anything that is not a live event, so an alert that disappears on its own fails outright there rather than conditionally.
Test it with the sound on
Turn on a screen reader, trigger the action, and listen. Markup can look correct and announce nothing at all, so this is the only check that settles it. Two cautions before you call it done. Some of the behaviors here are specific to one browser and screen reader pair, including the automatic Alert prefix, the double speaking when aria-live and role="alert" are combined, and the silence on a page that loaded with its alert already in place. One pass with one screen reader will not find all three.
Where the Message Should Live
Next to whatever it refers to. A form error beside its field. A confirmation where the action happened. The corner of the viewport is a layout convenience, not a reading position, and it is the furthest point on the page from where the person was looking.
For form errors specifically, a summary at the top that links to each broken field serves everybody, and our forms guide covers the shape of it. Remember which route you took, though. If that summary takes focus, it needs no live region around it and adding one says everything twice.