Handle accessibility, how should screen readers announce new toasts
Toasts appear without user action, so screen readers won't notice them unless you use an ARIA live region. Put a permanent live-region container in the DOM (role='status'/aria-live='polite' for info, role='alert'/aria-live='assertive' for errors) and inject toast text into it. Don't move focus to the toast, and keep it on screen long enough to be read.
A toast is a notification that appears without the user doing anything — which is exactly why it's an accessibility trap. A sighted user sees it slide in; a screen reader user has no idea it exists unless you explicitly announce it.
The mechanism: ARIA live regions
A live region is an element the screen reader watches. When its content changes, the SR announces the change without moving focus.
<!-- Mounted ONCE, lives for the app's lifetime, usually empty -->
<div id="toast-region" aria-live="polite" aria-atomic="true"></div>When a toast fires, you inject its text into that region:
function announce(message) {
document.getElementById("toast-region").textContent = message;
}Critical rule: the live-region container must already be in the DOM before the content changes. If you mount the container and its text in the same render, many screen readers miss it.
politeness: polite vs assertive
| Type | Attribute | When |
|---|---|---|
| Info / success | aria-live="polite" or role="status" | Waits for a pause, then announces. "Item added to cart." |
| Error / urgent | aria-live="assertive" or role="alert" | Interrupts immediately. "Payment failed." |
Default to polite. Reserve assertive for things the user must hear now — overusing it makes the app feel like it's shouting.
role="status" implies aria-live="polite"; role="alert" implies aria-live="assertive". Using the role is often cleaner.
A React pattern
function ToastRegion({ toasts }: { toasts: Toast[] }) {
return (
<>
{/* Visible toasts */}
<div className="toast-stack" aria-hidden="true">
{toasts.map((t) => <ToastCard key={t.id} toast={t} />)}
</div>
{/* The announcer — visually hidden, always mounted */}
<div className="sr-only" role="status" aria-live="polite">
{toasts.filter((t) => t.type !== "error").map((t) => t.message).join(", ")}
</div>
<div className="sr-only" role="alert" aria-live="assertive">
{toasts.filter((t) => t.type === "error").map((t) => t.message).join(", ")}
</div>
</>
);
}Note the visible stack is aria-hidden so the toast text isn't announced twice (once visually-mapped, once via the region).
Other rules
- Do NOT move focus to the toast. It's non-modal; stealing focus interrupts what the user is doing. (A modal alert dialog is different.)
- Don't auto-dismiss too fast. WCAG 2.2.1: give the user enough time. ~5s minimum for short messages; for important ones, let the user dismiss manually and don't auto-dismiss at all. If a toast has an action ("Undo"), it must be keyboard-reachable, which means it can't disappear on a timer the user can't control.
.sr-onlymust not bedisplay:none— that removes it from the accessibility tree. Use the clip/position technique.aria-atomic="true"makes the SR read the whole region, not just the diff — usually what you want for a single message.
Senior framing
The senior answer nails three things: (1) toasts are silent to SR users by default, so a persistent live region is mandatory; (2) polite vs assertive is a deliberate UX choice, not a default; (3) you don't trap or move focus for a non-modal toast, but you also respect WCAG timing so the message and any action stay reachable. Bonus points for knowing the container must pre-exist in the DOM.
Follow-up questions
- •Why must the live-region container exist in the DOM before the message is added?
- •When is an alert dialog (with focus trap) the right choice instead of a toast?
- •How do you handle a toast with an 'Undo' action accessibly?
- •What does aria-atomic do?
Common mistakes
- •Mounting the toast text and its live-region container in the same render — the change isn't announced.
- •Moving focus to the toast, interrupting the user's task.
- •Using assertive for every toast, making the SR constantly interrupt.
- •Auto-dismissing before a screen reader user can hear it or reach its action button.
- •Using display:none for .sr-only, which hides it from assistive tech entirely.
Edge cases
- •Rapid-fire toasts can overwhelm the announcer — debounce or queue messages.
- •A toast with an action button needs the toast to persist until dismissed.
- •Duplicate announcements if both the visible toast and the live region are exposed to AT.
Real-world examples
- •'Added to cart' confirmations, 'Message sent', form save indicators, 'Connection lost' banners.