← Default view
Conformance audit · WCAG 2.2 AA · ARIA APG

Toast Report Card

Five requirements. Seven React toast libraries. The two that pass are the two nobody installs.

2 / 7 pass all five
ScopeAnnouncement · keyboard · timing MethodSource read + issue tracker Exhibits32 cited Issued2026-07-13 StatusNo library ships compliant by default
§1

The exam

what a correct toast must do
REQ-01

A live region that already exists

The region must be in the DOM before the message arrives. Dynamically adding an already-populated role="alert" node "generally does not lead to an announcement" [7].

REQ-02

Polite by default, assertive by exception

role="status" for everything that isn't urgent. Assertive interrupts mid-sentence and assistive tech may discard queued messages [9][5].

REQ-03

A keyboard route to the toast

A toast portalled to the end of <body> is after everything in tab order and may vanish first. The answer is a landmark plus a hotkey — F6, F8, Alt+T [29].

REQ-04

A timer that pauses on focus

Pause-on-hover is a mouse feature. The accessible version pauses on hover and focus and window blur [16].

REQ-05

No auto-dismiss you can't survive

An auto-dismissing toast is a content-set time limit. SC 2.2.1 (Level A) wants it off, ×10-adjustable or extendable — unless the information exists elsewhere [1].

§2

The matrix

graded against shipped defaults, not what you can configure
Requirement 7 libraries · main branch, Jul 2026 Base UI⭐ 10.3k React Aria⭐ 15.7k · alpha Radix⭐ 19.1k Sonner⭐ 12.6k react-toastify⭐ 13.4k react-hot-toast⭐ 11k notistack⭐ 4.1k
REQ-01 Pre-mounted live region Region in the DOM before the message [7] Viewport aria-live="polite" + separate role="alert" region [31] alert region alongside a non-modal alertdialog [13] ~Portal'd announce node, text on the next frame for NVDA [15] — but #3634 reports aria-live="off" [14] Persistent <section aria-live="polite">, fixed after #306 [17] Container carries aria-live since v11 [24] ARIA props spread onto the message div that mounts with its text [26] role="alert" on a node that mounts already populated [28]
REQ-02 Polite default / assertive exception Errors may interrupt. Success may not [5] priority: 'low' | 'high' [30] Priority split, documented [29] type: 'foreground' | 'background' → assertive / polite [16] ~Polite only. No assertive anywhere in the source — toast.error() waits its turn [18] Inverted. Every toast defaults to role="alert" — your success message interrupts [21] ~Right polarity (status/polite) but no split; #336 open on the ARIA API [25] alert for all variants, including success [28]
REQ-03 Keyboard route Landmark + hotkey, named in the label [29] F6 force-focuses the viewport landmark [31] F6/⇧F6; focus restored on last close [29] F8, and the hotkey is in the region's accessible name [15] Alt+T; container tabIndex={-1}, toasts tabIndex={0} [18] Alt+T, added in v11 (Dec 2024) [23] Plain <div>s. No landmark, no hotkey, no tabIndex [26] No landmark, no hotkey [28]
REQ-04 Pause on focus Not just hover [16] Hover + focus + document hidden [30] Hover + focus + window blur [29] "Pauses closing on hover, focus and window blur" [16] Full matrix incl. isDocumentHiddenpauseTimer() [18] pauseOnFocusLoss is window blur, not element focus [21] onMouseEnter / onMouseLeave only [26] MUI Snackbar onMouseEnter only [28]
REQ-05 Survivable timing SC 2.2.1, Level A [1] ~5000 ms; 0 disables auto-dismiss [30] No auto-dismiss by default; docs say don't auto-dismiss critical info [29] ~5000 ms default [15] 4000 ms. Flagged as a 2.2.1 failure by the Apr 2026 audit [20] ~5000 ms default [24] 4000 ms (errors too; loading is ∞) [27] ~5000 ms (MUI Snackbar) [28]
Grade A9 / 10 A10 / 10 B8 / 10 C7 / 10 D5 / 10 F1 / 10 F1 / 10
meets the requirement out of the box (2 pts) ~ partial — right idea, wrong default (1 pt) fails (0 pts)

Every cell in REQ-05 grades a developer default. Not one of the seven exposes an end-user duration control — which is what SC 2.2.1 actually asks for. The escape hatch is real, though: a timer needs no adjustment "if there is an alternative that does not rely on a timer" [1]. A toast that is the only carrier of its information has no such alternative.

REQ-04 is worth less than it looks. A screen-reader user reading a toast with the virtual cursor generates no focus event. Pause-on-focus never fires for them; they are racing the timer like everyone else [10].

Finding

The toast shadcn/ui puts in your app scores C. The two that score A ship in almost nobody's.

Radix ⭐ 19.1k has the most stars and the right design — and an open bug saying its own docs demo announces nothing [14]. Popularity in this category tracked one scaffolding default, not one audit.

§3

Grades, with the evidence

verbatim from source, docs and open issues
A

Base UI Toast

⭐ 10.3k · 9/10 · stable

The correct model. Each toast root is a non-modal dialog (or alertdialog when priority is high), so its buttons stay reachable [32] — and the announcement lives in a separate pre-mounted region. Only demerit: raise the 5 s default.

Exhibit · ToastRoot.tsx
role: isHighPriority ? 'alertdialog' : 'dialog'
A

React Aria Toast

⭐ 15.7k · 10/10 · still alpha

Best documented, least shippable. Same split model — alert region inside a non-modal alertdialog [13] — plus F6 landmark cycling and focus restoration on the last close. The only library whose docs tell you not to auto-dismiss. It is also the only one still in alpha.

Exhibit · React Aria docs
"Only auto-dismiss toasts when the information is not critical, or may be found elsewhere."
B

Radix Toast

⭐ 19.1k · 8/10 · open bug #3634

Right design, unverified delivery. Hard-codes role="status" for every toast on purpose and modulates urgency through aria-live instead; renders the announce text on the next frame to beat NVDA's mount-time swallow [15]. Then #3634 (open, July 2025) reports aria-live="off" landing on that very node in the official demo, across Chrome, Safari and Firefox [14]. Pin-check your version and test with a real screen reader.

Exhibit · toast.tsx, line comment
// Toasts are always role=status to avoid stuttering issues with role=alert in SRs.
C

Sonner

⭐ 12.6k · 7/10 · the default everyone ships

Polite about everything, including your errors. There is no assertive path anywhere in src/index.tsxtoast.error() queues behind whatever the screen reader is already reading [18]. Default lifetime 4000 ms, flagged as an SC 2.2.1 failure [20]. Good bones otherwise, and the only library in the batch shipping its own reduced-motion guard [19]. Set duration={8000} and closeButton; don't route urgent errors through it.

Exhibit · src/index.tsx
const TOAST_LIFETIME = 4000;
aria-live="polite" aria-atomic="false"
D

react-toastify

⭐ 13.4k · 5/10 · fixable in one prop

Half-fixed, and wrong in the half that's left. v11 (Dec 2024) gave the container a real live region and added Alt+T [23] — then defaults every toast to role="alert", so a "Saved!" interrupts the sentence a user was listening to [21]. Issue #1122 (open) reports those role="alert" toasts announced by nothing at all in Firefox + Orca [22]. Set role="status" globally and it climbs.

Exhibit · ToastContainer.tsx defaults
role: 'alert', 'aria-label': 'Notifications Alt+T'
F

react-hot-toast

⭐ 11k · 1/10 · avoid where a11y is graded

Worst of the group. No persistent live region — the ARIA props are spread onto the message div that mounts already holding its text, i.e. the exact pattern MDN says produces no announcement [7]. No landmark, no hotkey, toasts aren't focusable, pause fires only on onMouseEnter [26]. Its Undo buttons are decorative for keyboard users.

Exhibit · src/core/toast.ts
ariaProps: { role: 'status', 'aria-live': 'polite' }
— spread onto a node that mounts populated
F

notistack

⭐ 4.1k · 1/10 · assertive-everything

The textbook anti-pattern, on every variant. role="alert" sits directly on the snackbar content element, which mounts already populated — for success and info too [28]. So it is simultaneously the most interrupting design on paper and, per MDN, the one most likely to announce nothing at all.

Exhibit · MaterialDesignContent.tsx
role="alert" aria-describedby={ariaDescribedby}
§4

Two findings no library can fix

structural, not implementation

A live region cannot carry a button

alert, status and log all map to live regions, and a screen reader announces the raw text within — "without any of the structure" [11]. Your Undo button is announced as the word Undo, with no route to reach it.

If it has an interactive child, it should not be a live region — and it should not be a toast.

That is why Base UI and React Aria render the toast as a non-modal dialog and put the announcement in a separate region [32]. The dialog holds the buttons; the region holds the text. Everyone else picks one and loses the other. Scott O'Hara's narrower allowance: a toast action is acceptable only if the same action is also available in the primary document [10].

The standard hasn't decided either

Patrick Lauke asked the working group whether a disappearing toast is a timeout at all. w3c/wcag#976 has been open since 2019 with no resolution [2]. Auditors are not waiting: an April 2026 shadcn/ui audit fails Sonner's 4 s default outright [20].

Meanwhile the most-cited response to the whole pattern came from a design system, not a spec: GitHub removed toasts from Primer in late 2025, "because of their accessibility and usability issues" [11].

Roselli's failure list for a typical toast: 1.3.2 · 1.4.4 · 1.4.10 · 2.1.1 · 2.2.1 · 2.4.3 · 3.2.4 · 4.1.2 · 4.1.3
§5

Remediation

what to do on Monday, whichever library you're on
  1. Toast = text only. role="status", injected into a region that was already there. No buttons in the live region [7].

  2. Duplicate every toast action in the page. The undone row keeps its own Undo control [10].

  3. Errors that block the user are not toasts. They are inline, focus-managed messages.

  4. Auto-dismiss ≥ 8 s, pausable on hover and focus, with a close button — or no auto-dismiss at all when the toast is the sole carrier of the information [1][20].

  5. Ship the landmark hotkey and name it in the region label. A hotkey nobody is told about is not an affordance [15].

  6. Test the combination you ship. aria-live is advisory — "only strong suggestions" — and browsers and assistive tech override it [9]. Firefox + Orca is where react-toastify goes silent; NVDA is where mount-time announcements vanish [22][13].

If your toast has an Undo button and a 4-second timer, no library choice will save you.

§6

Evidence log

32 exhibits
[1]WCAG 2.2 — Understanding SC 2.2.1 Timing AdjustableA timer needs no adjustment if a non-timed alternative to the information exists.official
[2]w3c/wcag#976 — "does a disappearing toast represent a timeout?"Open since 2019. No working-group resolution.forum
[3]WCAG 2.2 — Understanding SC 2.2.3 No TimingLevel AAA: timing must not be essential to the activity.official
[4]WCAG 2.2 — Understanding SC 4.1.3 Status MessagesLevel AA: status must be announced without receiving focus.official
[5]ARIA APG — Alert patternKeyboard interaction: "Not applicable". Avoid alerts that disappear automatically.official
[6]ARIA APG — Alert exampleThe alert element is pre-rendered empty; the message is inserted into it.official
[7]MDN — ARIA: alert roleDo not dynamically add an already-populated role=alert: "this generally does not lead to an announcement".official
[8]Sara Soueidan — ARIA live regions, part 1Place the live region in the DOM as early as possible, then populate it.vendor-blog
[9]Sara Soueidan — ARIA live regions, part 2Assertive may discard queued messages; aria-live is advisory only.vendor-blog
[10]Scott O'Hara — A toast to a11y toastsA toast is a status message; virtual-cursor reading fires no focus event.vendor-blog
[11]Adrian Roselli — Defining toast messagesLive regions announce raw text; GitHub removed toasts from Primer in late 2025.vendor-blog
[12]WCAG 2.2 — SC 2.4.11 Focus Not ObscuredA toast stack over a focused control is a straight AA failure.official
[13]Sandro Roth — Building a React toast componentNVDA does not announce a role=status element added already populated.vendor-blog
[14]radix-ui/primitives#3634 — toast not announcedOpen: aria-live="off" on the role=status element, in the official demo.forum
[15]radix-ui/primitives — toast.tsxrole=status always; announce text on the next frame; hotkey F8 named in the label.official
[16]Radix Primitives — Toast docs"Pauses closing on hover, focus and window blur."official
[17]emilkowalski/sonner#306 — live region must always be presentSilent toasts in Windows Narrator and older iOS VoiceOver. Fixed.forum
[18]emilkowalski/sonner — src/index.tsxTOAST_LIFETIME 4000; aria-live=polite; no assertive anywhere; hotkey alt+T.official
[19]emilkowalski/sonner — src/styles.cssThe only prefers-reduced-motion guard shipped by any library in the batch.official
[20]The Front Kit — shadcn/ui accessibility audit (Apr 2026)Sonner's 4 s default is a SC 2.2.1 failure; recommends 8 s plus a close button.vendor-blog
[21]react-toastify — Accessibility docs"By default, all toasts are displayed with the ARIA role alert."official
[22]fkhadra/react-toastify#1122 — silent in Firefox + OrcaOpen. role=alert toasts announced in Chrome+Orca, not Firefox+Orca.forum
[23]react-toastify — releasesv11 (Dec 2024) added ariaLabel and the Alt+T focus hotkey.official
[24]react-toastify — ToastContainer.tsxPersistent container live region; defaults role 'alert'.official
[25]timolins/react-hot-toast#336 — incoherent ARIA APIOpen since Jan 2024. role=alert plus aria-live=assertive is redundant.forum
[26]react-hot-toast — src/core/toast.tsariaProps spread onto a Message element that mounts already containing its text.official
[27]react-hot-toast — src/core/store.tsblank 4000, error 4000, custom 4000, loading Infinity.official
[28]notistack — MaterialDesignContent.tsxrole="alert" on a populated node, for every variant including success.official
[29]React Aria — ToastF6 / Shift+F6 landmark navigation; focus restored on last close; 5 s minimum.official
[30]Base UI — ToastF6 into the viewport landmark; priority low/high; timeout 5000, 0 disables.official
[31]Base UI — ToastViewport.tsxPolite viewport plus a separate role="alert" region for high-priority toasts.official
[32]Base UI — ToastRoot.tsxEach toast root is a non-modal dialog / alertdialog, so its buttons stay reachable.official