fix(a11y): don't force atomic re-announce of the whole toast stack

/code-review caught that role="status" on the toast container implies
aria-atomic="true", so every new toast made a screen reader re-read the entire
stack instead of just the new one — the opposite of the U-F fix's intent. Use a bare
aria-live="polite" (defaults atomic=false, announcing only the added toast); error
toasts keep role="alert". Also simplified the conditional role to a plain ternary.
This commit is contained in:
2026-07-21 23:36:27 +02:00
parent 6ed08383e7
commit e832b401ac
+5 -5
View File
@@ -18,11 +18,11 @@ export default function Toaster() {
return (
// Stable, always-mounted polite live region — a polite region must PRE-EXIST in the DOM for a
// screen reader to announce content later inserted into it (a region created together with its
// first message is unreliably announced). Non-error toasts render inside here and are announced
// politely; error/fatal toasts additionally carry role="alert", which announces assertively on
// insertion regardless.
// first message is unreliably announced). `aria-live` (not role="status") on purpose:
// role="status" implies aria-atomic="true", which would re-read the WHOLE stack on every toast;
// aria-live defaults atomic=false, so only the newly-added toast is spoken. Error/fatal toasts
// additionally carry role="alert", which announces assertively on insertion regardless.
<div
role="status"
aria-live="polite"
className="absolute bottom-4 left-4 z-overlay flex flex-col gap-2 w-80 max-w-[calc(100vw-2rem)]"
>
@@ -32,7 +32,7 @@ export default function Toaster() {
return (
<div
key={toast.id}
{...(urgent ? { role: "alert" } : {})}
role={urgent ? "alert" : undefined}
className="toast-card glass relative overflow-hidden rounded-xl px-3 py-3 flex items-start gap-3 animate-[popIn_0.16s_ease]"
>
<Icon className={`w-5 h-5 shrink-0 mt-0.5 ${color}`} />