When to use
A toast is the right answer for brief, non-blocking confirmation of an action the user just took. “Saved.” “Sent.” “Copied to clipboard.” The user doesn’t need to do anything; we’re just acknowledging.
Don’t use a toast for:
- Critical errors that need a decision — use a modal
<dialog>or inline error in the form. - Information the user might miss — toasts are intentionally fleeting. If they have to act on it, anchor it visibly until acted on.
- Long content — keep toast text to a sentence. Anything more belongs in a panel.
The pattern
The trick to an accessible toast is the live region — a container with aria-live whose content changes are announced by screen readers without focus moving:
<!-- Polite: queues behind whatever the user is currently being told -->
<div role="status" aria-live="polite" aria-relevant="additions text" id="toast-region"></div>
<!-- Assertive: interrupts. Reserve for genuine errors. -->
<div role="alert" aria-live="assertive" aria-relevant="additions text" id="toast-region-alert"></div> The regions are present empty, on every page. When you append a child to one, the screen reader announces the added text:
function toast(message, { variant = 'info' } = {}) {
const region = document.getElementById(
variant === 'error' ? 'toast-region-alert' : 'toast-region'
);
const el = document.createElement('div');
el.className = `toast toast--${variant}`;
el.textContent = message;
region.appendChild(el);
setTimeout(() => el.remove(), 6000);
} Polite vs assertive
| Use for | What it does | |
|---|---|---|
aria-live="polite" (role="status") | Most things — “Saved”, “Copied”, confirmations | Queued; interrupts nothing |
aria-live="assertive" (role="alert") | Genuine errors that the user must hear right now | Interrupts current speech |
Default to polite. Assertive announcements interrupt whatever the screen reader was reading; overuse of them is exhausting. The error toast in our demo uses role="alert" because failed saves are worth interrupting for; the others are polite.
Don’t move focus
A toast is a passive notification. Moving keyboard focus to it yanks the user out of whatever they were doing — usually the worst possible UX, especially mid-form-fill. The live region announces the message; focus stays put.
If the user wants to act on the toast (dismiss it, click “Undo”), they can Tab to it — the dismiss button is in the tab order after the trigger, so the discovery flow works.
Dismiss + auto-dismiss
WCAG 2.2.1 says timing must be adjustable. Auto-dismissing toasts have a timer; here’s how to stay compliant:
- Default duration ≥ 5 seconds — no shorter than that.
- Provide a dismiss button — explicit user control on every toast.
- Pause on hover and focus — if the user is reading or has Tab’d into the toast, don’t time out.
- Honor
prefers-reduced-motion— disable the slide-in animation, but the toast and announcement still work.
Stack management
If multiple toasts arrive in quick succession, render them all in the region. The screen reader announces each as it arrives. Visually, stack them with the newest at the top. Cap the visible count (4–5) and drop the oldest off-screen visually if needed; the live region will have already announced them.
Anti-patterns
A <div> that just appears
<div class="toast">Settings saved.</div> No role, no live region, no announcement. Sighted users see it; screen-reader users have no idea the save happened. The fix is one attribute: role="status".
Live region created on demand
// DON'T — screen readers may not register the region
const live = document.createElement('div');
live.setAttribute('aria-live', 'polite');
live.textContent = 'Saved!';
document.body.appendChild(live); Live regions need to be in the DOM before their content changes. Most screen readers don’t announce a region that didn’t exist when the page loaded. Render the empty region in your layout; populate it as needed.
Auto-dismissing in 2 seconds
A toast that appears for 1.5 seconds is invisible to slow readers, anyone using magnification, or screen-reader users who haven’t finished hearing the previous announcement. Five seconds minimum, with hover/focus pausing it.
Assertive everything
aria-live="assertive" interrupts the user. If your “Saved” toast interrupts the user’s screen reader mid-sentence, they hate you. Reserve it for actual errors that need attention now.
Common variations
- Toasts with actions — “File deleted. Undo” — the Undo control is a real
<button>inside the toast. Pause auto-dismiss when the toast has actions, or extend the timer to ≥ 10 seconds. Keyboard users need time to Tab to it. - Persistent banner — for system-wide states (“You’re offline”), don’t use a toast. Use a banner that stays until the state ends, with a real
role="alert"orrole="status"if it appears dynamically. - Top vs bottom corner — visual placement is up to you, but ensure it doesn’t overlap focus indicators or hit-target areas.
- Fixed positioning + scrollable content — if the page scrolls behind a fixed toast, ensure the toast doesn’t cover essential controls (WCAG 2.4.11).
Checklist
- Live region exists in the DOM on page load (don’t create on demand)
-
role="status"(polite) for confirmations,role="alert"(assertive) for errors - Toast is not focused programmatically; focus stays where the user put it
- Each toast has a dismiss button with an accessible name
- Auto-dismiss ≥ 5 seconds, paused on hover/focus
- Dismiss button reachable via Tab
- Animation respects
prefers-reduced-motion - Multiple toasts: visible stacking with newest first, all announced