[{"data":1,"prerenderedAt":3092},["ShallowReactive",2],{"site-header-nav":3,"page-\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Faccessible-toast-notifications-in-react\u002F":314,"content-navigation":2940},[4,84,88,216],{"title":5,"path":6,"stem":7,"children":8},"Core Accessibility Principles For Modern Frameworks","\u002Fcore-accessibility-principles-for-modern-frameworks","core-accessibility-principles-for-modern-frameworks",[9,12,18,24,36,48,66,78],{"title":10,"path":6,"stem":11},"Core Accessibility Principles for Modern Frameworks","core-accessibility-principles-for-modern-frameworks\u002Findex",{"title":13,"path":14,"stem":15,"children":16},"Accessible Color Contrast & Theming","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Faccessible-color-contrast-theming","core-accessibility-principles-for-modern-frameworks\u002Faccessible-color-contrast-theming\u002Findex",[17],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":22},"Accessible Form Validation & Error States","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Faccessible-form-validation-error-states","core-accessibility-principles-for-modern-frameworks\u002Faccessible-form-validation-error-states\u002Findex",[23],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":28},"Focus Management Strategies for SPAs","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas","core-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas\u002Findex",[29,30],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":34},"Handling Focus Restoration After Dynamic Route Changes","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas\u002Fhandling-focus-restoration-after-dynamic-route-changes","core-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas\u002Fhandling-focus-restoration-after-dynamic-route-changes\u002Findex",[35],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":40},"Keyboard Navigation Patterns for Modals","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fkeyboard-navigation-patterns-for-modals","core-accessibility-principles-for-modern-frameworks\u002Fkeyboard-navigation-patterns-for-modals\u002Findex",[41,42],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":46},"Building Accessible Dropdowns Without External UI Kits","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fkeyboard-navigation-patterns-for-modals\u002Fbuilding-accessible-dropdowns-without-external-ui-kits","core-accessibility-principles-for-modern-frameworks\u002Fkeyboard-navigation-patterns-for-modals\u002Fbuilding-accessible-dropdowns-without-external-ui-kits\u002Findex",[47],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":52},"Reduced Motion & Animation Accessibility","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Freduced-motion-and-animation-accessibility","core-accessibility-principles-for-modern-frameworks\u002Freduced-motion-and-animation-accessibility\u002Findex",[53,54,60],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":58},"Accessible Loading Skeletons and Spinners","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Freduced-motion-and-animation-accessibility\u002Faccessible-loading-skeletons-and-spinners","core-accessibility-principles-for-modern-frameworks\u002Freduced-motion-and-animation-accessibility\u002Faccessible-loading-skeletons-and-spinners\u002Findex",[59],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":64},"Respecting prefers-reduced-motion in React and CSS","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Freduced-motion-and-animation-accessibility\u002Frespecting-prefers-reduced-motion-in-react-and-css","core-accessibility-principles-for-modern-frameworks\u002Freduced-motion-and-animation-accessibility\u002Frespecting-prefers-reduced-motion-in-react-and-css\u002Findex",[65],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":70},"Screen Reader Compatibility Testing","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fscreen-reader-compatibility-testing","core-accessibility-principles-for-modern-frameworks\u002Fscreen-reader-compatibility-testing\u002Findex",[71,72],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":76},"Testing ARIA Live Regions with Jest and Testing Library","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fscreen-reader-compatibility-testing\u002Ftesting-aria-live-regions-with-jest-and-testing-library","core-accessibility-principles-for-modern-frameworks\u002Fscreen-reader-compatibility-testing\u002Ftesting-aria-live-regions-with-jest-and-testing-library\u002Findex",[77],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":82},"Semantic HTML vs ARIA in Component Trees","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fsemantic-html-vs-aria-in-component-trees","core-accessibility-principles-for-modern-frameworks\u002Fsemantic-html-vs-aria-in-component-trees\u002Findex",[83],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87},"Modern Framework Accessibility","\u002F","index",{"title":89,"path":90,"stem":91,"children":92},"React Nextjs Accessibility Patterns","\u002Freact-nextjs-accessibility-patterns","react-nextjs-accessibility-patterns",[93,96,108,132,156,162,180,204],{"title":94,"path":90,"stem":95},"React & Next.js Accessibility Patterns","react-nextjs-accessibility-patterns\u002Findex",{"title":97,"path":98,"stem":99,"children":100},"Accessible Component Libraries in React","\u002Freact-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react","react-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react\u002Findex",[101,102],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":106},"Building Accessible Tabs in React Without Radix UI","\u002Freact-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react\u002Fbuilding-accessible-tabs-in-react-without-radix-ui","react-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react\u002Fbuilding-accessible-tabs-in-react-without-radix-ui\u002Findex",[107],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":112},"Accessible Data Tables & Grids in React","\u002Freact-nextjs-accessibility-patterns\u002Faccessible-data-tables-and-grids","react-nextjs-accessibility-patterns\u002Faccessible-data-tables-and-grids\u002Findex",[113,114,120,126],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":118},"Accessible Pagination for React Data Tables","\u002Freact-nextjs-accessibility-patterns\u002Faccessible-data-tables-and-grids\u002Faccessible-pagination-for-react-data-tables","react-nextjs-accessibility-patterns\u002Faccessible-data-tables-and-grids\u002Faccessible-pagination-for-react-data-tables\u002Findex",[119],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":124},"Building a Sortable Accessible Data Table in React","\u002Freact-nextjs-accessibility-patterns\u002Faccessible-data-tables-and-grids\u002Fbuilding-a-sortable-accessible-data-table-in-react","react-nextjs-accessibility-patterns\u002Faccessible-data-tables-and-grids\u002Fbuilding-a-sortable-accessible-data-table-in-react\u002Findex",[125],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":130},"Virtualizing Long Lists Accessibly in React","\u002Freact-nextjs-accessibility-patterns\u002Faccessible-data-tables-and-grids\u002Fvirtualizing-long-lists-accessibly-in-react","react-nextjs-accessibility-patterns\u002Faccessible-data-tables-and-grids\u002Fvirtualizing-long-lists-accessibly-in-react\u002Findex",[131],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":136},"Dynamic Content & State Announcements","\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements","react-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Findex",[137,138,144,150],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":142},"Accessible Toast Notifications in React","\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Faccessible-toast-notifications-in-react","react-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Faccessible-toast-notifications-in-react\u002Findex",[143],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":148},"Announcing Client-Side Route Changes in React","\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Fannouncing-client-side-route-changes-in-react","react-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Fannouncing-client-side-route-changes-in-react\u002Findex",[149],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":154},"React Context for Accessibility Preferences","\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Freact-context-for-global-accessibility-preferences","react-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Freact-context-for-global-accessibility-preferences\u002Findex",[155],{"title":151,"path":152,"stem":153},{"title":157,"path":158,"stem":159,"children":160},"Form Handling with React Hook Form & A11y","\u002Freact-nextjs-accessibility-patterns\u002Fform-handling-with-react-hook-form-a11y","react-nextjs-accessibility-patterns\u002Fform-handling-with-react-hook-form-a11y\u002Findex",[161],{"title":157,"path":158,"stem":159},{"title":163,"path":164,"stem":165,"children":166},"Next.js App Router Accessibility","\u002Freact-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y","react-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Findex",[167,168,174],{"title":163,"path":164,"stem":165},{"title":169,"path":170,"stem":171,"children":172},"Skip Links in Next.js App Router","\u002Freact-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Fimplementing-skip-links-in-nextjs-app-router","react-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Fimplementing-skip-links-in-nextjs-app-router\u002Findex",[173],{"title":169,"path":170,"stem":171},{"title":175,"path":176,"stem":177,"children":178},"Next.js Dynamic Imports & Keyboard Navigation","\u002Freact-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Fnextjs-dynamic-imports-and-keyboard-navigation","react-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Fnextjs-dynamic-imports-and-keyboard-navigation\u002Findex",[179],{"title":175,"path":176,"stem":177},{"title":181,"path":182,"stem":183,"children":184},"React Hooks for Accessibility","\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility","react-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Findex",[185,186,192,198],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189,"children":190},"Building a useAnnouncer Hook for Live Regions","\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Fbuilding-a-useannouncer-hook-for-live-regions","react-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Fbuilding-a-useannouncer-hook-for-live-regions\u002Findex",[191],{"title":187,"path":188,"stem":189},{"title":193,"path":194,"stem":195,"children":196},"Fixing Focus Trap Issues in React Portals","\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Ffixing-focus-trap-issues-in-react-portals","react-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Ffixing-focus-trap-issues-in-react-portals\u002Findex",[197],{"title":193,"path":194,"stem":195},{"title":199,"path":200,"stem":201,"children":202},"Making React useEffect Accessible for Screen Readers","\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Fmaking-react-useeffect-accessible-for-screen-readers","react-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Fmaking-react-useeffect-accessible-for-screen-readers\u002Findex",[203],{"title":199,"path":200,"stem":201},{"title":205,"path":206,"stem":207,"children":208},"Server Components & Client-Side Interactivity","\u002Freact-nextjs-accessibility-patterns\u002Fserver-components-client-side-interactivity","react-nextjs-accessibility-patterns\u002Fserver-components-client-side-interactivity\u002Findex",[209,210],{"title":205,"path":206,"stem":207},{"title":211,"path":212,"stem":213,"children":214},"Accessible Modals in Next.js 14 Server Components","\u002Freact-nextjs-accessibility-patterns\u002Fserver-components-client-side-interactivity\u002Fhandling-accessible-modals-in-nextjs-14-server-components","react-nextjs-accessibility-patterns\u002Fserver-components-client-side-interactivity\u002Fhandling-accessible-modals-in-nextjs-14-server-components\u002Findex",[215],{"title":211,"path":212,"stem":213},{"title":217,"path":218,"stem":219,"children":220},"Testing And Automating Accessibility","\u002Ftesting-and-automating-accessibility","testing-and-automating-accessibility",[221,224,242,260,278,296],{"title":222,"path":218,"stem":223},"Testing & Automating Accessibility","testing-and-automating-accessibility\u002Findex",{"title":225,"path":226,"stem":227,"children":228},"Accessibility Audits with Lighthouse","\u002Ftesting-and-automating-accessibility\u002Faccessibility-audits-with-lighthouse","testing-and-automating-accessibility\u002Faccessibility-audits-with-lighthouse\u002Findex",[229,230,236],{"title":225,"path":226,"stem":227},{"title":231,"path":232,"stem":233,"children":234},"Interpreting Lighthouse Accessibility Scores","\u002Ftesting-and-automating-accessibility\u002Faccessibility-audits-with-lighthouse\u002Finterpreting-lighthouse-accessibility-scores","testing-and-automating-accessibility\u002Faccessibility-audits-with-lighthouse\u002Finterpreting-lighthouse-accessibility-scores\u002Findex",[235],{"title":231,"path":232,"stem":233},{"title":237,"path":238,"stem":239,"children":240},"Setting Lighthouse CI Accessibility Budgets","\u002Ftesting-and-automating-accessibility\u002Faccessibility-audits-with-lighthouse\u002Fsetting-lighthouse-ci-accessibility-budgets","testing-and-automating-accessibility\u002Faccessibility-audits-with-lighthouse\u002Fsetting-lighthouse-ci-accessibility-budgets\u002Findex",[241],{"title":237,"path":238,"stem":239},{"title":243,"path":244,"stem":245,"children":246},"Automated Accessibility Testing with axe-core","\u002Ftesting-and-automating-accessibility\u002Fautomated-accessibility-testing-with-axe-core","testing-and-automating-accessibility\u002Fautomated-accessibility-testing-with-axe-core\u002Findex",[247,248,254],{"title":243,"path":244,"stem":245},{"title":249,"path":250,"stem":251,"children":252},"Catching Color Contrast Failures with axe-core","\u002Ftesting-and-automating-accessibility\u002Fautomated-accessibility-testing-with-axe-core\u002Fcatching-color-contrast-failures-with-axe-core","testing-and-automating-accessibility\u002Fautomated-accessibility-testing-with-axe-core\u002Fcatching-color-contrast-failures-with-axe-core\u002Findex",[253],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257,"children":258},"Writing Custom axe-core Rules","\u002Ftesting-and-automating-accessibility\u002Fautomated-accessibility-testing-with-axe-core\u002Fwriting-custom-axe-core-rules","testing-and-automating-accessibility\u002Fautomated-accessibility-testing-with-axe-core\u002Fwriting-custom-axe-core-rules\u002Findex",[259],{"title":255,"path":256,"stem":257},{"title":261,"path":262,"stem":263,"children":264},"Component Testing with jest-axe","\u002Ftesting-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe","testing-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002Findex",[265,266,272],{"title":261,"path":262,"stem":263},{"title":267,"path":268,"stem":269,"children":270},"Debugging jest-axe Violations in CI","\u002Ftesting-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002Fdebugging-jest-axe-violations-in-ci","testing-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002Fdebugging-jest-axe-violations-in-ci\u002Findex",[271],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275,"children":276},"Testing React Components with jest-axe","\u002Ftesting-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002Ftesting-react-components-with-jest-axe","testing-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002Ftesting-react-components-with-jest-axe\u002Findex",[277],{"title":273,"path":274,"stem":275},{"title":279,"path":280,"stem":281,"children":282},"End-to-End Accessibility Testing with Playwright","\u002Ftesting-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright","testing-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002Findex",[283,284,290],{"title":279,"path":280,"stem":281},{"title":285,"path":286,"stem":287,"children":288},"Asserting Focus Order in Playwright","\u002Ftesting-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002Fasserting-focus-order-in-playwright","testing-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002Fasserting-focus-order-in-playwright\u002Findex",[289],{"title":285,"path":286,"stem":287},{"title":291,"path":292,"stem":293,"children":294},"Keyboard Navigation Tests in Playwright","\u002Ftesting-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002Fkeyboard-navigation-tests-in-playwright","testing-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002Fkeyboard-navigation-tests-in-playwright\u002Findex",[295],{"title":291,"path":292,"stem":293},{"title":297,"path":298,"stem":299,"children":300},"Gating Accessibility in CI\u002FCD Pipelines","\u002Ftesting-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines","testing-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines\u002Findex",[301,302,308],{"title":297,"path":298,"stem":299},{"title":303,"path":304,"stem":305,"children":306},"Accessibility Regression Testing in GitHub Actions","\u002Ftesting-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines\u002Faccessibility-regression-testing-in-github-actions","testing-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines\u002Faccessibility-regression-testing-in-github-actions\u002Findex",[307],{"title":303,"path":304,"stem":305},{"title":309,"path":310,"stem":311,"children":312},"Failing Pull Requests on axe Violations","\u002Ftesting-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines\u002Ffailing-pull-requests-on-axe-violations","testing-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines\u002Ffailing-pull-requests-on-axe-violations\u002Findex",[313],{"title":309,"path":310,"stem":311},{"id":315,"title":139,"body":316,"date":2933,"description":2934,"extension":2935,"image":2933,"meta":2936,"modifiedAt":2933,"navigation":467,"noindex":2937,"path":140,"publishedAt":2933,"seo":2938,"stem":141,"updatedAt":2933,"__hash__":2939},"content\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Faccessible-toast-notifications-in-react\u002Findex.md",{"type":317,"value":318,"toc":2922},"minimark",[319,323,349,360,363,368,371,425,432,434,438,441,1309,1319,1321,1325,1331,1712,1722,1725,1812,1814,1818,1821,2054,2065,2079,2350,2365,2367,2371,2374,2380,2733,2745,2751,2753,2757,2818,2820,2824,2838,2840,2844,2855,2866,2871,2874,2879,2882,2887,2892,2894,2898,2918],[320,321,139],"h1",{"id":322},"accessible-toast-notifications-in-react",[324,325,326,327,331,332,336,337,340,341,344,345,348],"p",{},"Toast notifications are deceptively hard to get right. The visual layer is trivial—a positioned ",[328,329,330],"code",{},"div",", a slide-in transition, an auto-dismiss timer—but the accessibility layer is where most implementations silently fail. A toast that screen reader users never hear is not a notification; it is decoration. This guide shows how to build React toasts that announce reliably through assistive technology, distinguish routine confirmations from urgent errors, never steal keyboard focus, and respect users who read slowly. The patterns here build on ",[333,334,133],"a",{"href":335},"\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002F"," and the wider set of ",[333,338,94],{"href":339},"\u002Freact-nextjs-accessibility-patterns\u002F",", and they map directly to WCAG ",[328,342,343],{},"4.1.3 Status Messages"," and ",[328,346,347],{},"2.2.1 Timing Adjustable",".",[324,350,351,352,356,357,359],{},"The central insight is that toasts are ",[353,354,355],"em",{},"status messages",": content that conveys a change in application state without moving focus. WCAG ",[328,358,343],{}," (Level AA) requires that such messages be programmatically determinable through role or properties so assistive technology can announce them without a focus change. Get the live region semantics right and the rest is incremental.",[361,362],"hr",{},[364,365,367],"h2",{"id":366},"prerequisites","Prerequisites",[324,369,370],{},"Before implementing, ensure your environment and mental model are in place:",[372,373,374,386,397,415],"ul",{},[375,376,377,381,382,385],"li",{},[378,379,380],"strong",{},"React 18+"," with a client component boundary (",[328,383,384],{},"'use client'"," in Next.js App Router).",[375,387,388,389,392,393,396],{},"A ",[378,390,391],{},"portal target"," at the document root so toasts render above the rest of the tree without inheriting ",[328,394,395],{},"overflow: hidden"," or stacking-context traps.",[375,398,399,400,403,404,407,408,403,411,414],{},"Familiarity with the two ARIA roles that matter here: ",[328,401,402],{},"role=\"status\""," (implicit ",[328,405,406],{},"aria-live=\"polite\"",") and ",[328,409,410],{},"role=\"alert\"",[328,412,413],{},"aria-live=\"assertive\"",").",[375,416,388,417,420,421,424],{},[328,418,419],{},".sr-only"," utility class is ",[378,422,423],{},"not"," required for visible toasts, but you will need correct contrast and focus handling for the dismiss control.",[324,426,427,428,431],{},"A critical rule before any code: ",[378,429,430],{},"the live region container must already exist in the DOM before you write text into it."," Screen readers register live regions when they are inserted into the accessibility tree. If you mount the container and its message in the same render, many AT\u002Fbrowser combinations miss the announcement entirely. Render the empty region container at app startup; mutate its children later.",[361,433],{},[364,435,437],{"id":436},"the-live-region-container","The Live Region Container",[324,439,440],{},"Mount a persistent region pair once, near the root. Routine toasts feed the polite region; errors feed the assertive region. Keeping both permanently in the tree is what makes announcements dependable.",[442,443,448],"pre",{"className":444,"code":445,"language":446,"meta":447,"style":447},"language-tsx shiki shiki-themes github-light github-dark","'use client';\n\nimport { createContext, useCallback, useContext, useRef, useState, type ReactNode } from 'react';\n\ntype ToastTone = 'info' | 'success' | 'error';\n\ninterface Toast {\n  id: number;\n  message: string;\n  tone: ToastTone;\n}\n\ninterface ToastApi {\n  notify: (message: string, tone?: ToastTone) => void;\n}\n\nconst ToastContext = createContext\u003CToastApi | null>(null);\n\nlet nextId = 0;\n\nexport function ToastProvider({ children }: { children: ReactNode }) {\n  const [toasts, setToasts] = useState\u003CToast[]>([]);\n  const timers = useRef\u003CMap\u003Cnumber, ReturnType\u003Ctypeof setTimeout>>>(new Map());\n\n  const dismiss = useCallback((id: number) => {\n    setToasts((prev) => prev.filter((t) => t.id !== id));\n    const timer = timers.current.get(id);\n    if (timer) {\n      clearTimeout(timer);\n      timers.current.delete(id);\n    }\n  }, []);\n\n  const notify = useCallback(\n    (message: string, tone: ToastTone = 'info') => {\n      const id = nextId++;\n      setToasts((prev) => [...prev, { id, message, tone }]);\n      \u002F\u002F Errors stay until dismissed; routine toasts auto-expire (see 2.2.1).\n      if (tone !== 'error') {\n        const timer = setTimeout(() => dismiss(id), 6000);\n        timers.current.set(id, timer);\n      }\n    },\n    [dismiss],\n  );\n\n  return (\n    \u003CToastContext.Provider value={{ notify }}>\n      {children}\n      \u003CToastViewport toasts={toasts} onDismiss={dismiss} \u002F>\n    \u003C\u002FToastContext.Provider>\n  );\n}\n\nexport function useToast(): ToastApi {\n  const ctx = useContext(ToastContext);\n  if (!ctx) throw new Error('useToast must be used within a ToastProvider');\n  return ctx;\n}\n","tsx","",[328,449,450,462,469,493,498,526,531,543,559,572,584,590,595,605,645,650,655,689,694,711,716,752,785,830,835,864,903,923,932,941,952,958,964,969,984,1014,1033,1055,1062,1078,1106,1118,1124,1130,1136,1142,1147,1156,1173,1179,1204,1215,1220,1225,1230,1249,1265,1296,1304],{"__ignoreMap":447},[451,452,455,458],"span",{"class":453,"line":454},"line",1,[451,456,384],{"class":457},"sZZnC",[451,459,461],{"class":460},"sVt8B",";\n",[451,463,465],{"class":453,"line":464},2,[451,466,468],{"emptyLinePlaceholder":467},true,"\n",[451,470,472,476,479,482,485,488,491],{"class":453,"line":471},3,[451,473,475],{"class":474},"szBVR","import",[451,477,478],{"class":460}," { createContext, useCallback, useContext, useRef, useState, ",[451,480,481],{"class":474},"type",[451,483,484],{"class":460}," ReactNode } ",[451,486,487],{"class":474},"from",[451,489,490],{"class":457}," 'react'",[451,492,461],{"class":460},[451,494,496],{"class":453,"line":495},4,[451,497,468],{"emptyLinePlaceholder":467},[451,499,501,503,507,510,513,516,519,521,524],{"class":453,"line":500},5,[451,502,481],{"class":474},[451,504,506],{"class":505},"sScJk"," ToastTone",[451,508,509],{"class":474}," =",[451,511,512],{"class":457}," 'info'",[451,514,515],{"class":474}," |",[451,517,518],{"class":457}," 'success'",[451,520,515],{"class":474},[451,522,523],{"class":457}," 'error'",[451,525,461],{"class":460},[451,527,529],{"class":453,"line":528},6,[451,530,468],{"emptyLinePlaceholder":467},[451,532,534,537,540],{"class":453,"line":533},7,[451,535,536],{"class":474},"interface",[451,538,539],{"class":505}," Toast",[451,541,542],{"class":460}," {\n",[451,544,546,550,553,557],{"class":453,"line":545},8,[451,547,549],{"class":548},"s4XuR","  id",[451,551,552],{"class":474},":",[451,554,556],{"class":555},"sj4cs"," number",[451,558,461],{"class":460},[451,560,562,565,567,570],{"class":453,"line":561},9,[451,563,564],{"class":548},"  message",[451,566,552],{"class":474},[451,568,569],{"class":555}," string",[451,571,461],{"class":460},[451,573,575,578,580,582],{"class":453,"line":574},10,[451,576,577],{"class":548},"  tone",[451,579,552],{"class":474},[451,581,506],{"class":505},[451,583,461],{"class":460},[451,585,587],{"class":453,"line":586},11,[451,588,589],{"class":460},"}\n",[451,591,593],{"class":453,"line":592},12,[451,594,468],{"emptyLinePlaceholder":467},[451,596,598,600,603],{"class":453,"line":597},13,[451,599,536],{"class":474},[451,601,602],{"class":505}," ToastApi",[451,604,542],{"class":460},[451,606,608,611,613,616,619,621,623,626,629,632,634,637,640,643],{"class":453,"line":607},14,[451,609,610],{"class":505},"  notify",[451,612,552],{"class":474},[451,614,615],{"class":460}," (",[451,617,618],{"class":548},"message",[451,620,552],{"class":474},[451,622,569],{"class":555},[451,624,625],{"class":460},", ",[451,627,628],{"class":548},"tone",[451,630,631],{"class":474},"?:",[451,633,506],{"class":505},[451,635,636],{"class":460},") ",[451,638,639],{"class":474},"=>",[451,641,642],{"class":555}," void",[451,644,461],{"class":460},[451,646,648],{"class":453,"line":647},15,[451,649,589],{"class":460},[451,651,653],{"class":453,"line":652},16,[451,654,468],{"emptyLinePlaceholder":467},[451,656,658,661,664,666,669,672,675,677,680,683,686],{"class":453,"line":657},17,[451,659,660],{"class":474},"const",[451,662,663],{"class":555}," ToastContext",[451,665,509],{"class":474},[451,667,668],{"class":505}," createContext",[451,670,671],{"class":460},"\u003C",[451,673,674],{"class":505},"ToastApi",[451,676,515],{"class":474},[451,678,679],{"class":555}," null",[451,681,682],{"class":460},">(",[451,684,685],{"class":555},"null",[451,687,688],{"class":460},");\n",[451,690,692],{"class":453,"line":691},18,[451,693,468],{"emptyLinePlaceholder":467},[451,695,697,700,703,706,709],{"class":453,"line":696},19,[451,698,699],{"class":474},"let",[451,701,702],{"class":460}," nextId ",[451,704,705],{"class":474},"=",[451,707,708],{"class":555}," 0",[451,710,461],{"class":460},[451,712,714],{"class":453,"line":713},20,[451,715,468],{"emptyLinePlaceholder":467},[451,717,719,722,725,728,731,734,737,739,742,744,746,749],{"class":453,"line":718},21,[451,720,721],{"class":474},"export",[451,723,724],{"class":474}," function",[451,726,727],{"class":505}," ToastProvider",[451,729,730],{"class":460},"({ ",[451,732,733],{"class":548},"children",[451,735,736],{"class":460}," }",[451,738,552],{"class":474},[451,740,741],{"class":460}," { ",[451,743,733],{"class":548},[451,745,552],{"class":474},[451,747,748],{"class":505}," ReactNode",[451,750,751],{"class":460}," }) {\n",[451,753,755,758,761,764,766,769,772,774,777,779,782],{"class":453,"line":754},22,[451,756,757],{"class":474},"  const",[451,759,760],{"class":460}," [",[451,762,763],{"class":555},"toasts",[451,765,625],{"class":460},[451,767,768],{"class":555},"setToasts",[451,770,771],{"class":460},"] ",[451,773,705],{"class":474},[451,775,776],{"class":505}," useState",[451,778,671],{"class":460},[451,780,781],{"class":505},"Toast",[451,783,784],{"class":460},"[]>([]);\n",[451,786,788,790,793,795,798,800,803,805,808,810,813,815,818,821,824,827],{"class":453,"line":787},23,[451,789,757],{"class":474},[451,791,792],{"class":555}," timers",[451,794,509],{"class":474},[451,796,797],{"class":505}," useRef",[451,799,671],{"class":460},[451,801,802],{"class":505},"Map",[451,804,671],{"class":460},[451,806,807],{"class":555},"number",[451,809,625],{"class":460},[451,811,812],{"class":505},"ReturnType",[451,814,671],{"class":460},[451,816,817],{"class":474},"typeof",[451,819,820],{"class":460}," setTimeout>>>(",[451,822,823],{"class":474},"new",[451,825,826],{"class":505}," Map",[451,828,829],{"class":460},"());\n",[451,831,833],{"class":453,"line":832},24,[451,834,468],{"emptyLinePlaceholder":467},[451,836,838,840,843,845,848,851,854,856,858,860,862],{"class":453,"line":837},25,[451,839,757],{"class":474},[451,841,842],{"class":555}," dismiss",[451,844,509],{"class":474},[451,846,847],{"class":505}," useCallback",[451,849,850],{"class":460},"((",[451,852,853],{"class":548},"id",[451,855,552],{"class":474},[451,857,556],{"class":555},[451,859,636],{"class":460},[451,861,639],{"class":474},[451,863,542],{"class":460},[451,865,867,870,872,875,877,879,882,885,887,890,892,894,897,900],{"class":453,"line":866},26,[451,868,869],{"class":505},"    setToasts",[451,871,850],{"class":460},[451,873,874],{"class":548},"prev",[451,876,636],{"class":460},[451,878,639],{"class":474},[451,880,881],{"class":460}," prev.",[451,883,884],{"class":505},"filter",[451,886,850],{"class":460},[451,888,889],{"class":548},"t",[451,891,636],{"class":460},[451,893,639],{"class":474},[451,895,896],{"class":460}," t.id ",[451,898,899],{"class":474},"!==",[451,901,902],{"class":460}," id));\n",[451,904,906,909,912,914,917,920],{"class":453,"line":905},27,[451,907,908],{"class":474},"    const",[451,910,911],{"class":555}," timer",[451,913,509],{"class":474},[451,915,916],{"class":460}," timers.current.",[451,918,919],{"class":505},"get",[451,921,922],{"class":460},"(id);\n",[451,924,926,929],{"class":453,"line":925},28,[451,927,928],{"class":474},"    if",[451,930,931],{"class":460}," (timer) {\n",[451,933,935,938],{"class":453,"line":934},29,[451,936,937],{"class":505},"      clearTimeout",[451,939,940],{"class":460},"(timer);\n",[451,942,944,947,950],{"class":453,"line":943},30,[451,945,946],{"class":460},"      timers.current.",[451,948,949],{"class":505},"delete",[451,951,922],{"class":460},[451,953,955],{"class":453,"line":954},31,[451,956,957],{"class":460},"    }\n",[451,959,961],{"class":453,"line":960},32,[451,962,963],{"class":460},"  }, []);\n",[451,965,967],{"class":453,"line":966},33,[451,968,468],{"emptyLinePlaceholder":467},[451,970,972,974,977,979,981],{"class":453,"line":971},34,[451,973,757],{"class":474},[451,975,976],{"class":555}," notify",[451,978,509],{"class":474},[451,980,847],{"class":505},[451,982,983],{"class":460},"(\n",[451,985,987,990,992,994,996,998,1000,1002,1004,1006,1008,1010,1012],{"class":453,"line":986},35,[451,988,989],{"class":460},"    (",[451,991,618],{"class":548},[451,993,552],{"class":474},[451,995,569],{"class":555},[451,997,625],{"class":460},[451,999,628],{"class":548},[451,1001,552],{"class":474},[451,1003,506],{"class":505},[451,1005,509],{"class":474},[451,1007,512],{"class":457},[451,1009,636],{"class":460},[451,1011,639],{"class":474},[451,1013,542],{"class":460},[451,1015,1017,1020,1023,1025,1028,1031],{"class":453,"line":1016},36,[451,1018,1019],{"class":474},"      const",[451,1021,1022],{"class":555}," id",[451,1024,509],{"class":474},[451,1026,1027],{"class":460}," nextId",[451,1029,1030],{"class":474},"++",[451,1032,461],{"class":460},[451,1034,1036,1039,1041,1043,1045,1047,1049,1052],{"class":453,"line":1035},37,[451,1037,1038],{"class":505},"      setToasts",[451,1040,850],{"class":460},[451,1042,874],{"class":548},[451,1044,636],{"class":460},[451,1046,639],{"class":474},[451,1048,760],{"class":460},[451,1050,1051],{"class":474},"...",[451,1053,1054],{"class":460},"prev, { id, message, tone }]);\n",[451,1056,1058],{"class":453,"line":1057},38,[451,1059,1061],{"class":1060},"sJ8bj","      \u002F\u002F Errors stay until dismissed; routine toasts auto-expire (see 2.2.1).\n",[451,1063,1065,1068,1071,1073,1075],{"class":453,"line":1064},39,[451,1066,1067],{"class":474},"      if",[451,1069,1070],{"class":460}," (tone ",[451,1072,899],{"class":474},[451,1074,523],{"class":457},[451,1076,1077],{"class":460},") {\n",[451,1079,1081,1084,1086,1088,1091,1094,1096,1098,1101,1104],{"class":453,"line":1080},40,[451,1082,1083],{"class":474},"        const",[451,1085,911],{"class":555},[451,1087,509],{"class":474},[451,1089,1090],{"class":505}," setTimeout",[451,1092,1093],{"class":460},"(() ",[451,1095,639],{"class":474},[451,1097,842],{"class":505},[451,1099,1100],{"class":460},"(id), ",[451,1102,1103],{"class":555},"6000",[451,1105,688],{"class":460},[451,1107,1109,1112,1115],{"class":453,"line":1108},41,[451,1110,1111],{"class":460},"        timers.current.",[451,1113,1114],{"class":505},"set",[451,1116,1117],{"class":460},"(id, timer);\n",[451,1119,1121],{"class":453,"line":1120},42,[451,1122,1123],{"class":460},"      }\n",[451,1125,1127],{"class":453,"line":1126},43,[451,1128,1129],{"class":460},"    },\n",[451,1131,1133],{"class":453,"line":1132},44,[451,1134,1135],{"class":460},"    [dismiss],\n",[451,1137,1139],{"class":453,"line":1138},45,[451,1140,1141],{"class":460},"  );\n",[451,1143,1145],{"class":453,"line":1144},46,[451,1146,468],{"emptyLinePlaceholder":467},[451,1148,1150,1153],{"class":453,"line":1149},47,[451,1151,1152],{"class":474},"  return",[451,1154,1155],{"class":460}," (\n",[451,1157,1159,1162,1165,1168,1170],{"class":453,"line":1158},48,[451,1160,1161],{"class":460},"    \u003C",[451,1163,1164],{"class":555},"ToastContext.Provider",[451,1166,1167],{"class":505}," value",[451,1169,705],{"class":474},[451,1171,1172],{"class":460},"{{ notify }}>\n",[451,1174,1176],{"class":453,"line":1175},49,[451,1177,1178],{"class":460},"      {children}\n",[451,1180,1182,1185,1188,1191,1193,1196,1199,1201],{"class":453,"line":1181},50,[451,1183,1184],{"class":460},"      \u003C",[451,1186,1187],{"class":555},"ToastViewport",[451,1189,1190],{"class":505}," toasts",[451,1192,705],{"class":474},[451,1194,1195],{"class":460},"{toasts} ",[451,1197,1198],{"class":505},"onDismiss",[451,1200,705],{"class":474},[451,1202,1203],{"class":460},"{dismiss} \u002F>\n",[451,1205,1207,1210,1212],{"class":453,"line":1206},51,[451,1208,1209],{"class":460},"    \u003C\u002F",[451,1211,1164],{"class":555},[451,1213,1214],{"class":460},">\n",[451,1216,1218],{"class":453,"line":1217},52,[451,1219,1141],{"class":460},[451,1221,1223],{"class":453,"line":1222},53,[451,1224,589],{"class":460},[451,1226,1228],{"class":453,"line":1227},54,[451,1229,468],{"emptyLinePlaceholder":467},[451,1231,1233,1235,1237,1240,1243,1245,1247],{"class":453,"line":1232},55,[451,1234,721],{"class":474},[451,1236,724],{"class":474},[451,1238,1239],{"class":505}," useToast",[451,1241,1242],{"class":460},"()",[451,1244,552],{"class":474},[451,1246,602],{"class":505},[451,1248,542],{"class":460},[451,1250,1252,1254,1257,1259,1262],{"class":453,"line":1251},56,[451,1253,757],{"class":474},[451,1255,1256],{"class":555}," ctx",[451,1258,509],{"class":474},[451,1260,1261],{"class":505}," useContext",[451,1263,1264],{"class":460},"(ToastContext);\n",[451,1266,1268,1271,1273,1276,1279,1282,1285,1288,1291,1294],{"class":453,"line":1267},57,[451,1269,1270],{"class":474},"  if",[451,1272,615],{"class":460},[451,1274,1275],{"class":474},"!",[451,1277,1278],{"class":460},"ctx) ",[451,1280,1281],{"class":474},"throw",[451,1283,1284],{"class":474}," new",[451,1286,1287],{"class":505}," Error",[451,1289,1290],{"class":460},"(",[451,1292,1293],{"class":457},"'useToast must be used within a ToastProvider'",[451,1295,688],{"class":460},[451,1297,1299,1301],{"class":453,"line":1298},58,[451,1300,1152],{"class":474},[451,1302,1303],{"class":460}," ctx;\n",[451,1305,1307],{"class":453,"line":1306},59,[451,1308,589],{"class":460},[324,1310,1311,1312,1315,1316,1318],{},"The provider owns timers in a ref so re-renders never drop or duplicate them, and it separates ",[353,1313,1314],{},"error"," tone (persistent) from routine tones (auto-dismissing). That separation is the seed of ",[328,1317,347],{}," compliance, which we complete below.",[361,1320],{},[364,1322,1324],{"id":1323},"rendering-the-viewport-status-vs-alert","Rendering the Viewport: status vs. alert",[324,1326,1327,1328,348],{},"The viewport renders two distinct live regions. Polite toasts wait their turn so they never interrupt the user mid-sentence; assertive toasts cut in because the user must know ",[353,1329,1330],{},"now",[442,1332,1334],{"className":444,"code":1333,"language":446,"meta":447,"style":447},"function ToastViewport({\n  toasts,\n  onDismiss,\n}: {\n  toasts: Toast[];\n  onDismiss: (id: number) => void;\n}) {\n  const polite = toasts.filter((t) => t.tone !== 'error');\n  const assertive = toasts.filter((t) => t.tone === 'error');\n\n  return (\n    \u003C>\n      {\u002F* Routine confirmations: announced when the SR is idle (4.1.3). *\u002F}\n      \u003Cdiv role=\"status\" aria-live=\"polite\" aria-atomic=\"false\" className=\"toast-region\">\n        {polite.map((t) => (\n          \u003CToastItem key={t.id} toast={t} onDismiss={onDismiss} \u002F>\n        ))}\n      \u003C\u002Fdiv>\n\n      {\u002F* Errors interrupt immediately. role=\"alert\" implies assertive. *\u002F}\n      \u003Cdiv role=\"alert\" aria-live=\"assertive\" aria-atomic=\"false\" className=\"toast-region\">\n        {assertive.map((t) => (\n          \u003CToastItem key={t.id} toast={t} onDismiss={onDismiss} \u002F>\n        ))}\n      \u003C\u002Fdiv>\n    \u003C\u002F>\n  );\n}\n",[328,1335,1336,1347,1355,1362,1371,1382,1404,1409,1440,1470,1474,1480,1485,1495,1536,1554,1585,1590,1599,1603,1612,1646,1663,1687,1691,1699,1704,1708],{"__ignoreMap":447},[451,1337,1338,1341,1344],{"class":453,"line":454},[451,1339,1340],{"class":474},"function",[451,1342,1343],{"class":505}," ToastViewport",[451,1345,1346],{"class":460},"({\n",[451,1348,1349,1352],{"class":453,"line":464},[451,1350,1351],{"class":548},"  toasts",[451,1353,1354],{"class":460},",\n",[451,1356,1357,1360],{"class":453,"line":471},[451,1358,1359],{"class":548},"  onDismiss",[451,1361,1354],{"class":460},[451,1363,1364,1367,1369],{"class":453,"line":495},[451,1365,1366],{"class":460},"}",[451,1368,552],{"class":474},[451,1370,542],{"class":460},[451,1372,1373,1375,1377,1379],{"class":453,"line":500},[451,1374,1351],{"class":548},[451,1376,552],{"class":474},[451,1378,539],{"class":505},[451,1380,1381],{"class":460},"[];\n",[451,1383,1384,1386,1388,1390,1392,1394,1396,1398,1400,1402],{"class":453,"line":528},[451,1385,1359],{"class":505},[451,1387,552],{"class":474},[451,1389,615],{"class":460},[451,1391,853],{"class":548},[451,1393,552],{"class":474},[451,1395,556],{"class":555},[451,1397,636],{"class":460},[451,1399,639],{"class":474},[451,1401,642],{"class":555},[451,1403,461],{"class":460},[451,1405,1406],{"class":453,"line":533},[451,1407,1408],{"class":460},"}) {\n",[451,1410,1411,1413,1416,1418,1421,1423,1425,1427,1429,1431,1434,1436,1438],{"class":453,"line":545},[451,1412,757],{"class":474},[451,1414,1415],{"class":555}," polite",[451,1417,509],{"class":474},[451,1419,1420],{"class":460}," toasts.",[451,1422,884],{"class":505},[451,1424,850],{"class":460},[451,1426,889],{"class":548},[451,1428,636],{"class":460},[451,1430,639],{"class":474},[451,1432,1433],{"class":460}," t.tone ",[451,1435,899],{"class":474},[451,1437,523],{"class":457},[451,1439,688],{"class":460},[451,1441,1442,1444,1447,1449,1451,1453,1455,1457,1459,1461,1463,1466,1468],{"class":453,"line":561},[451,1443,757],{"class":474},[451,1445,1446],{"class":555}," assertive",[451,1448,509],{"class":474},[451,1450,1420],{"class":460},[451,1452,884],{"class":505},[451,1454,850],{"class":460},[451,1456,889],{"class":548},[451,1458,636],{"class":460},[451,1460,639],{"class":474},[451,1462,1433],{"class":460},[451,1464,1465],{"class":474},"===",[451,1467,523],{"class":457},[451,1469,688],{"class":460},[451,1471,1472],{"class":453,"line":574},[451,1473,468],{"emptyLinePlaceholder":467},[451,1475,1476,1478],{"class":453,"line":586},[451,1477,1152],{"class":474},[451,1479,1155],{"class":460},[451,1481,1482],{"class":453,"line":592},[451,1483,1484],{"class":460},"    \u003C>\n",[451,1486,1487,1490,1493],{"class":453,"line":597},[451,1488,1489],{"class":460},"      {",[451,1491,1492],{"class":1060},"\u002F* Routine confirmations: announced when the SR is idle (4.1.3). *\u002F",[451,1494,589],{"class":460},[451,1496,1497,1499,1502,1505,1507,1510,1513,1515,1518,1521,1523,1526,1529,1531,1534],{"class":453,"line":607},[451,1498,1184],{"class":460},[451,1500,330],{"class":1501},"s9eBZ",[451,1503,1504],{"class":505}," role",[451,1506,705],{"class":474},[451,1508,1509],{"class":457},"\"status\"",[451,1511,1512],{"class":505}," aria-live",[451,1514,705],{"class":474},[451,1516,1517],{"class":457},"\"polite\"",[451,1519,1520],{"class":505}," aria-atomic",[451,1522,705],{"class":474},[451,1524,1525],{"class":457},"\"false\"",[451,1527,1528],{"class":505}," className",[451,1530,705],{"class":474},[451,1532,1533],{"class":457},"\"toast-region\"",[451,1535,1214],{"class":460},[451,1537,1538,1541,1544,1546,1548,1550,1552],{"class":453,"line":647},[451,1539,1540],{"class":460},"        {polite.",[451,1542,1543],{"class":505},"map",[451,1545,850],{"class":460},[451,1547,889],{"class":548},[451,1549,636],{"class":460},[451,1551,639],{"class":474},[451,1553,1155],{"class":460},[451,1555,1556,1559,1562,1565,1567,1570,1573,1575,1578,1580,1582],{"class":453,"line":652},[451,1557,1558],{"class":460},"          \u003C",[451,1560,1561],{"class":555},"ToastItem",[451,1563,1564],{"class":505}," key",[451,1566,705],{"class":474},[451,1568,1569],{"class":460},"{t.id} ",[451,1571,1572],{"class":505},"toast",[451,1574,705],{"class":474},[451,1576,1577],{"class":460},"{t} ",[451,1579,1198],{"class":505},[451,1581,705],{"class":474},[451,1583,1584],{"class":460},"{onDismiss} \u002F>\n",[451,1586,1587],{"class":453,"line":657},[451,1588,1589],{"class":460},"        ))}\n",[451,1591,1592,1595,1597],{"class":453,"line":691},[451,1593,1594],{"class":460},"      \u003C\u002F",[451,1596,330],{"class":1501},[451,1598,1214],{"class":460},[451,1600,1601],{"class":453,"line":696},[451,1602,468],{"emptyLinePlaceholder":467},[451,1604,1605,1607,1610],{"class":453,"line":713},[451,1606,1489],{"class":460},[451,1608,1609],{"class":1060},"\u002F* Errors interrupt immediately. role=\"alert\" implies assertive. *\u002F",[451,1611,589],{"class":460},[451,1613,1614,1616,1618,1620,1622,1625,1627,1629,1632,1634,1636,1638,1640,1642,1644],{"class":453,"line":718},[451,1615,1184],{"class":460},[451,1617,330],{"class":1501},[451,1619,1504],{"class":505},[451,1621,705],{"class":474},[451,1623,1624],{"class":457},"\"alert\"",[451,1626,1512],{"class":505},[451,1628,705],{"class":474},[451,1630,1631],{"class":457},"\"assertive\"",[451,1633,1520],{"class":505},[451,1635,705],{"class":474},[451,1637,1525],{"class":457},[451,1639,1528],{"class":505},[451,1641,705],{"class":474},[451,1643,1533],{"class":457},[451,1645,1214],{"class":460},[451,1647,1648,1651,1653,1655,1657,1659,1661],{"class":453,"line":754},[451,1649,1650],{"class":460},"        {assertive.",[451,1652,1543],{"class":505},[451,1654,850],{"class":460},[451,1656,889],{"class":548},[451,1658,636],{"class":460},[451,1660,639],{"class":474},[451,1662,1155],{"class":460},[451,1664,1665,1667,1669,1671,1673,1675,1677,1679,1681,1683,1685],{"class":453,"line":787},[451,1666,1558],{"class":460},[451,1668,1561],{"class":555},[451,1670,1564],{"class":505},[451,1672,705],{"class":474},[451,1674,1569],{"class":460},[451,1676,1572],{"class":505},[451,1678,705],{"class":474},[451,1680,1577],{"class":460},[451,1682,1198],{"class":505},[451,1684,705],{"class":474},[451,1686,1584],{"class":460},[451,1688,1689],{"class":453,"line":832},[451,1690,1589],{"class":460},[451,1692,1693,1695,1697],{"class":453,"line":837},[451,1694,1594],{"class":460},[451,1696,330],{"class":1501},[451,1698,1214],{"class":460},[451,1700,1701],{"class":453,"line":866},[451,1702,1703],{"class":460},"    \u003C\u002F>\n",[451,1705,1706],{"class":453,"line":905},[451,1707,1141],{"class":460},[451,1709,1710],{"class":453,"line":925},[451,1711,589],{"class":460},[324,1713,1714,1715,1718,1719,1721],{},"Use ",[328,1716,1717],{},"aria-atomic=\"false\""," so the screen reader announces only the newly inserted toast, not the entire region every time a toast is added or removed. The choice of role is not cosmetic: reserve ",[328,1720,410],{}," for genuine errors and time-sensitive warnings. Overusing assertive announcements is itself an accessibility defect because it trains users to ignore interruptions.",[324,1723,1724],{},"The diagram below shows how the same user action routes to different politeness levels.",[1726,1727,1734,1735,1734,1739,1734,1743,1734,1754,1734,1762,1734,1768,1734,1771,1734,1777,1734,1780,1734,1785,1734,1787,1734,1790,1734,1794,1734,1800,1734,1803,1734,1807],"svg",{"role":1728,"ariaLabelledBy":1729,"viewBox":1732,"style":1733},"img",[1730,1731],"toastFlowT","toastFlowD","0 0 760 220","width:100%;height:auto;max-width:760px","\n  ",[1736,1737,1738],"title",{"id":1730},"Toast routing by tone",[1740,1741,1742],"desc",{"id":1731},"An application event splits into two paths: routine toasts enter a polite role=status region announced when the screen reader is idle, while errors enter an assertive role=alert region announced immediately.",[1744,1745],"rect",{"style":1746,"x":1747,"y":1748,"width":1749,"height":1750,"rx":1751,"fill":1752,"stroke":1753},"stroke-width:2","20","90","150","44","6","var(--primary-soft)","currentColor",[1755,1756,1761],"text",{"style":1757,"x":1758,"y":1759,"fill":1760},"text-anchor:middle","95","117","var(--text)","App event",[453,1763],{"style":1746,"x1":1764,"y1":1765,"x2":1766,"y2":1767,"stroke":1753},"170","112","280","50",[453,1769],{"style":1746,"x1":1764,"y1":1765,"x2":1766,"y2":1770,"stroke":1753},"174",[1744,1772],{"style":1746,"x":1766,"y":1773,"width":1774,"height":1775,"rx":1751,"fill":1776,"stroke":1753},"28","220","48","var(--surface)",[1755,1778,402],{"style":1757,"x":1779,"y":1767,"fill":1760},"390",[1755,1781,1784],{"style":1757,"x":1779,"y":1782,"fill":1783},"68","var(--muted)","polite — when idle",[1744,1786],{"style":1746,"x":1766,"y":1749,"width":1774,"height":1775,"rx":1751,"fill":1776,"stroke":1753},[1755,1788,410],{"style":1757,"x":1779,"y":1789,"fill":1760},"172",[1755,1791,1793],{"style":1757,"x":1779,"y":1792,"fill":1783},"190","assertive — immediately",[453,1795],{"style":1746,"x1":1796,"y1":1797,"x2":1798,"y2":1799,"stroke":1753},"500","52","600","100",[453,1801],{"style":1746,"x1":1796,"y1":1770,"x2":1798,"y2":1802,"stroke":1753},"120",[1744,1804],{"style":1746,"x":1798,"y":1805,"width":1806,"height":1775,"rx":1751,"fill":1752,"stroke":1753},"86","140",[1755,1808,1811],{"style":1757,"x":1809,"y":1810,"fill":1760},"670","115","Screen reader",[361,1813],{},[364,1815,1817],{"id":1816},"focus-safe-dismissal-and-timing","Focus-Safe Dismissal and Timing",[324,1819,1820],{},"A toast must never steal focus. Moving focus to a toast on appearance violates user expectations and can yank a keyboard or screen reader user out of the form they are completing. Instead, the toast item is a non-focusable container with a single focusable dismiss button that carries an accessible name.",[442,1822,1824],{"className":444,"code":1823,"language":446,"meta":447,"style":447},"function ToastItem({ toast, onDismiss }: { toast: Toast; onDismiss: (id: number) => void }) {\n  return (\n    \u003Cdiv className={`toast toast--${toast.tone}`}>\n      \u003Cspan className=\"toast__message\">{toast.message}\u003C\u002Fspan>\n      \u003Cbutton\n        type=\"button\"\n        className=\"toast__dismiss\"\n        onClick={() => onDismiss(toast.id)}\n        \u002F\u002F Accessible name independent of the icon glyph.\n        aria-label={`Dismiss notification: ${toast.message}`}\n      >\n        \u003Cspan aria-hidden=\"true\">×\u003C\u002Fspan>\n      \u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  );\n}\n",[328,1825,1826,1876,1882,1910,1930,1937,1947,1957,1975,1980,2002,2007,2029,2038,2046,2050],{"__ignoreMap":447},[451,1827,1828,1830,1833,1835,1837,1839,1841,1843,1845,1847,1849,1851,1853,1856,1858,1860,1862,1864,1866,1868,1870,1872,1874],{"class":453,"line":454},[451,1829,1340],{"class":474},[451,1831,1832],{"class":505}," ToastItem",[451,1834,730],{"class":460},[451,1836,1572],{"class":548},[451,1838,625],{"class":460},[451,1840,1198],{"class":548},[451,1842,736],{"class":460},[451,1844,552],{"class":474},[451,1846,741],{"class":460},[451,1848,1572],{"class":548},[451,1850,552],{"class":474},[451,1852,539],{"class":505},[451,1854,1855],{"class":460},"; ",[451,1857,1198],{"class":505},[451,1859,552],{"class":474},[451,1861,615],{"class":460},[451,1863,853],{"class":548},[451,1865,552],{"class":474},[451,1867,556],{"class":555},[451,1869,636],{"class":460},[451,1871,639],{"class":474},[451,1873,642],{"class":555},[451,1875,751],{"class":460},[451,1877,1878,1880],{"class":453,"line":464},[451,1879,1152],{"class":474},[451,1881,1155],{"class":460},[451,1883,1884,1886,1888,1890,1892,1895,1898,1900,1902,1904,1907],{"class":453,"line":471},[451,1885,1161],{"class":460},[451,1887,330],{"class":1501},[451,1889,1528],{"class":505},[451,1891,705],{"class":474},[451,1893,1894],{"class":460},"{",[451,1896,1897],{"class":457},"`toast toast--${",[451,1899,1572],{"class":460},[451,1901,348],{"class":457},[451,1903,628],{"class":460},[451,1905,1906],{"class":457},"}`",[451,1908,1909],{"class":460},"}>\n",[451,1911,1912,1914,1916,1918,1920,1923,1926,1928],{"class":453,"line":495},[451,1913,1184],{"class":460},[451,1915,451],{"class":1501},[451,1917,1528],{"class":505},[451,1919,705],{"class":474},[451,1921,1922],{"class":457},"\"toast__message\"",[451,1924,1925],{"class":460},">{toast.message}\u003C\u002F",[451,1927,451],{"class":1501},[451,1929,1214],{"class":460},[451,1931,1932,1934],{"class":453,"line":500},[451,1933,1184],{"class":460},[451,1935,1936],{"class":1501},"button\n",[451,1938,1939,1942,1944],{"class":453,"line":528},[451,1940,1941],{"class":505},"        type",[451,1943,705],{"class":474},[451,1945,1946],{"class":457},"\"button\"\n",[451,1948,1949,1952,1954],{"class":453,"line":533},[451,1950,1951],{"class":505},"        className",[451,1953,705],{"class":474},[451,1955,1956],{"class":457},"\"toast__dismiss\"\n",[451,1958,1959,1962,1964,1967,1969,1972],{"class":453,"line":545},[451,1960,1961],{"class":505},"        onClick",[451,1963,705],{"class":474},[451,1965,1966],{"class":460},"{() ",[451,1968,639],{"class":474},[451,1970,1971],{"class":505}," onDismiss",[451,1973,1974],{"class":460},"(toast.id)}\n",[451,1976,1977],{"class":453,"line":561},[451,1978,1979],{"class":1060},"        \u002F\u002F Accessible name independent of the icon glyph.\n",[451,1981,1982,1985,1987,1989,1992,1994,1996,1998,2000],{"class":453,"line":574},[451,1983,1984],{"class":505},"        aria-label",[451,1986,705],{"class":474},[451,1988,1894],{"class":460},[451,1990,1991],{"class":457},"`Dismiss notification: ${",[451,1993,1572],{"class":460},[451,1995,348],{"class":457},[451,1997,618],{"class":460},[451,1999,1906],{"class":457},[451,2001,589],{"class":460},[451,2003,2004],{"class":453,"line":586},[451,2005,2006],{"class":460},"      >\n",[451,2008,2009,2012,2014,2017,2019,2022,2025,2027],{"class":453,"line":592},[451,2010,2011],{"class":460},"        \u003C",[451,2013,451],{"class":1501},[451,2015,2016],{"class":505}," aria-hidden",[451,2018,705],{"class":474},[451,2020,2021],{"class":457},"\"true\"",[451,2023,2024],{"class":460},">×\u003C\u002F",[451,2026,451],{"class":1501},[451,2028,1214],{"class":460},[451,2030,2031,2033,2036],{"class":453,"line":597},[451,2032,1594],{"class":460},[451,2034,2035],{"class":1501},"button",[451,2037,1214],{"class":460},[451,2039,2040,2042,2044],{"class":453,"line":607},[451,2041,1209],{"class":460},[451,2043,330],{"class":1501},[451,2045,1214],{"class":460},[451,2047,2048],{"class":453,"line":647},[451,2049,1141],{"class":460},[451,2051,2052],{"class":453,"line":652},[451,2053,589],{"class":460},[324,2055,2056,2057,2060,2061,2064],{},"The ",[328,2058,2059],{},"aria-label"," gives the button a meaningful name even though it renders a bare glyph; the glyph itself is hidden from AT with ",[328,2062,2063],{},"aria-hidden=\"true\""," so it is not double-announced.",[324,2066,2067,2068,2070,2071,2074,2075,2078],{},"For ",[328,2069,347],{},", three properties matter. First, ",[378,2072,2073],{},"errors never auto-dismiss","—the provider above leaves them in the DOM until the user dismisses them. Second, routine toasts use a generous default (six seconds is a reasonable floor; the WCAG technique baseline is twenty seconds for content the user must read, so calibrate to message length). Third, ",[378,2076,2077],{},"pause on hover and focus"," so a user who is reading is never cut off. Pausing also satisfies users with cognitive or reading disabilities who need extra time.",[442,2080,2082],{"className":444,"code":2081,"language":446,"meta":447,"style":447},"\u002F\u002F Extend ToastProvider with hover\u002Ffocus pause for the routine region.\nfunction ToastViewportTimed({ toasts, onDismiss, onPauseAll, onResumeAll }: {\n  toasts: Toast[];\n  onDismiss: (id: number) => void;\n  onPauseAll: () => void;\n  onResumeAll: () => void;\n}) {\n  return (\n    \u003Cdiv\n      role=\"status\"\n      aria-live=\"polite\"\n      aria-atomic=\"false\"\n      className=\"toast-region\"\n      \u002F\u002F Reading the toast pauses every active dismissal timer (2.2.1).\n      onMouseEnter={onPauseAll}\n      onMouseLeave={onResumeAll}\n      onFocusCapture={onPauseAll}\n      onBlurCapture={onResumeAll}\n    >\n      {toasts.map((t) => (\n        \u003CToastItem key={t.id} toast={t} onDismiss={onDismiss} \u002F>\n      ))}\n    \u003C\u002Fdiv>\n  );\n}\n",[328,2083,2084,2089,2120,2130,2152,2168,2183,2187,2193,2200,2210,2220,2230,2240,2245,2255,2265,2274,2283,2288,2305,2329,2334,2342,2346],{"__ignoreMap":447},[451,2085,2086],{"class":453,"line":454},[451,2087,2088],{"class":1060},"\u002F\u002F Extend ToastProvider with hover\u002Ffocus pause for the routine region.\n",[451,2090,2091,2093,2096,2098,2100,2102,2104,2106,2109,2111,2114,2116,2118],{"class":453,"line":464},[451,2092,1340],{"class":474},[451,2094,2095],{"class":505}," ToastViewportTimed",[451,2097,730],{"class":460},[451,2099,763],{"class":548},[451,2101,625],{"class":460},[451,2103,1198],{"class":548},[451,2105,625],{"class":460},[451,2107,2108],{"class":548},"onPauseAll",[451,2110,625],{"class":460},[451,2112,2113],{"class":548},"onResumeAll",[451,2115,736],{"class":460},[451,2117,552],{"class":474},[451,2119,542],{"class":460},[451,2121,2122,2124,2126,2128],{"class":453,"line":471},[451,2123,1351],{"class":548},[451,2125,552],{"class":474},[451,2127,539],{"class":505},[451,2129,1381],{"class":460},[451,2131,2132,2134,2136,2138,2140,2142,2144,2146,2148,2150],{"class":453,"line":495},[451,2133,1359],{"class":505},[451,2135,552],{"class":474},[451,2137,615],{"class":460},[451,2139,853],{"class":548},[451,2141,552],{"class":474},[451,2143,556],{"class":555},[451,2145,636],{"class":460},[451,2147,639],{"class":474},[451,2149,642],{"class":555},[451,2151,461],{"class":460},[451,2153,2154,2157,2159,2162,2164,2166],{"class":453,"line":500},[451,2155,2156],{"class":505},"  onPauseAll",[451,2158,552],{"class":474},[451,2160,2161],{"class":460}," () ",[451,2163,639],{"class":474},[451,2165,642],{"class":555},[451,2167,461],{"class":460},[451,2169,2170,2173,2175,2177,2179,2181],{"class":453,"line":528},[451,2171,2172],{"class":505},"  onResumeAll",[451,2174,552],{"class":474},[451,2176,2161],{"class":460},[451,2178,639],{"class":474},[451,2180,642],{"class":555},[451,2182,461],{"class":460},[451,2184,2185],{"class":453,"line":533},[451,2186,1408],{"class":460},[451,2188,2189,2191],{"class":453,"line":545},[451,2190,1152],{"class":474},[451,2192,1155],{"class":460},[451,2194,2195,2197],{"class":453,"line":561},[451,2196,1161],{"class":460},[451,2198,2199],{"class":1501},"div\n",[451,2201,2202,2205,2207],{"class":453,"line":574},[451,2203,2204],{"class":505},"      role",[451,2206,705],{"class":474},[451,2208,2209],{"class":457},"\"status\"\n",[451,2211,2212,2215,2217],{"class":453,"line":586},[451,2213,2214],{"class":505},"      aria-live",[451,2216,705],{"class":474},[451,2218,2219],{"class":457},"\"polite\"\n",[451,2221,2222,2225,2227],{"class":453,"line":592},[451,2223,2224],{"class":505},"      aria-atomic",[451,2226,705],{"class":474},[451,2228,2229],{"class":457},"\"false\"\n",[451,2231,2232,2235,2237],{"class":453,"line":597},[451,2233,2234],{"class":505},"      className",[451,2236,705],{"class":474},[451,2238,2239],{"class":457},"\"toast-region\"\n",[451,2241,2242],{"class":453,"line":607},[451,2243,2244],{"class":1060},"      \u002F\u002F Reading the toast pauses every active dismissal timer (2.2.1).\n",[451,2246,2247,2250,2252],{"class":453,"line":647},[451,2248,2249],{"class":505},"      onMouseEnter",[451,2251,705],{"class":474},[451,2253,2254],{"class":460},"{onPauseAll}\n",[451,2256,2257,2260,2262],{"class":453,"line":652},[451,2258,2259],{"class":505},"      onMouseLeave",[451,2261,705],{"class":474},[451,2263,2264],{"class":460},"{onResumeAll}\n",[451,2266,2267,2270,2272],{"class":453,"line":657},[451,2268,2269],{"class":505},"      onFocusCapture",[451,2271,705],{"class":474},[451,2273,2254],{"class":460},[451,2275,2276,2279,2281],{"class":453,"line":691},[451,2277,2278],{"class":505},"      onBlurCapture",[451,2280,705],{"class":474},[451,2282,2264],{"class":460},[451,2284,2285],{"class":453,"line":696},[451,2286,2287],{"class":460},"    >\n",[451,2289,2290,2293,2295,2297,2299,2301,2303],{"class":453,"line":713},[451,2291,2292],{"class":460},"      {toasts.",[451,2294,1543],{"class":505},[451,2296,850],{"class":460},[451,2298,889],{"class":548},[451,2300,636],{"class":460},[451,2302,639],{"class":474},[451,2304,1155],{"class":460},[451,2306,2307,2309,2311,2313,2315,2317,2319,2321,2323,2325,2327],{"class":453,"line":718},[451,2308,2011],{"class":460},[451,2310,1561],{"class":555},[451,2312,1564],{"class":505},[451,2314,705],{"class":474},[451,2316,1569],{"class":460},[451,2318,1572],{"class":505},[451,2320,705],{"class":474},[451,2322,1577],{"class":460},[451,2324,1198],{"class":505},[451,2326,705],{"class":474},[451,2328,1584],{"class":460},[451,2330,2331],{"class":453,"line":754},[451,2332,2333],{"class":460},"      ))}\n",[451,2335,2336,2338,2340],{"class":453,"line":787},[451,2337,1209],{"class":460},[451,2339,330],{"class":1501},[451,2341,1214],{"class":460},[451,2343,2344],{"class":453,"line":832},[451,2345,1141],{"class":460},[451,2347,2348],{"class":453,"line":837},[451,2349,589],{"class":460},[324,2351,2352,2353,2356,2357,2360,2361,2364],{},"To implement pause cleanly, store each timer's remaining duration rather than a fixed handle: on pause, ",[328,2354,2355],{},"clearTimeout"," and record ",[328,2358,2359],{},"remaining = expiresAt - Date.now()","; on resume, schedule a fresh timeout for ",[328,2362,2363],{},"remaining",". This keeps the timing logic correct across repeated hover cycles.",[361,2366],{},[364,2368,2370],{"id":2369},"how-to-verify","How to Verify",[324,2372,2373],{},"Automated checks catch structural regressions; manual checks confirm the announcement actually fires.",[324,2375,2376,2379],{},[378,2377,2378],{},"axe \u002F jest-axe."," Render the provider with a toast present and assert no violations, then assert the roles exist:",[442,2381,2383],{"className":444,"code":2382,"language":446,"meta":447,"style":447},"import { render, screen, act } from '@testing-library\u002Freact';\nimport { axe } from 'jest-axe';\nimport { ToastProvider, useToast } from '.\u002Ftoast';\n\nfunction Trigger() {\n  const { notify } = useToast();\n  return \u003Cbutton onClick={() => notify('Saved', 'success')}>Save\u003C\u002Fbutton>;\n}\n\ntest('toast region has no axe violations and announces politely', async () => {\n  const { container } = render(\n    \u003CToastProvider>\n      \u003CTrigger \u002F>\n    \u003C\u002FToastProvider>,\n  );\n  await act(async () => {\n    screen.getByText('Save').click();\n  });\n  expect(screen.getByRole('status')).toHaveTextContent('Saved');\n  expect(await axe(container)).toHaveNoViolations();\n});\n\ntest('errors render in an assertive alert region', async () => {\n  render(\u003CToastProvider>\u003CTrigger \u002F>\u003C\u002FToastProvider>);\n  \u002F\u002F ...trigger an error tone, then:\n  \u002F\u002F expect(screen.getByRole('alert')).toHaveTextContent('Connection lost');\n});\n",[328,2384,2385,2399,2413,2427,2431,2441,2460,2498,2502,2506,2527,2545,2554,2564,2573,2577,2595,2615,2620,2648,2668,2673,2677,2696,2719,2724,2729],{"__ignoreMap":447},[451,2386,2387,2389,2392,2394,2397],{"class":453,"line":454},[451,2388,475],{"class":474},[451,2390,2391],{"class":460}," { render, screen, act } ",[451,2393,487],{"class":474},[451,2395,2396],{"class":457}," '@testing-library\u002Freact'",[451,2398,461],{"class":460},[451,2400,2401,2403,2406,2408,2411],{"class":453,"line":464},[451,2402,475],{"class":474},[451,2404,2405],{"class":460}," { axe } ",[451,2407,487],{"class":474},[451,2409,2410],{"class":457}," 'jest-axe'",[451,2412,461],{"class":460},[451,2414,2415,2417,2420,2422,2425],{"class":453,"line":471},[451,2416,475],{"class":474},[451,2418,2419],{"class":460}," { ToastProvider, useToast } ",[451,2421,487],{"class":474},[451,2423,2424],{"class":457}," '.\u002Ftoast'",[451,2426,461],{"class":460},[451,2428,2429],{"class":453,"line":495},[451,2430,468],{"emptyLinePlaceholder":467},[451,2432,2433,2435,2438],{"class":453,"line":500},[451,2434,1340],{"class":474},[451,2436,2437],{"class":505}," Trigger",[451,2439,2440],{"class":460},"() {\n",[451,2442,2443,2445,2447,2450,2453,2455,2457],{"class":453,"line":528},[451,2444,757],{"class":474},[451,2446,741],{"class":460},[451,2448,2449],{"class":555},"notify",[451,2451,2452],{"class":460}," } ",[451,2454,705],{"class":474},[451,2456,1239],{"class":505},[451,2458,2459],{"class":460},"();\n",[451,2461,2462,2464,2467,2469,2472,2474,2476,2478,2480,2482,2485,2487,2490,2493,2495],{"class":453,"line":533},[451,2463,1152],{"class":474},[451,2465,2466],{"class":460}," \u003C",[451,2468,2035],{"class":1501},[451,2470,2471],{"class":505}," onClick",[451,2473,705],{"class":474},[451,2475,1966],{"class":460},[451,2477,639],{"class":474},[451,2479,976],{"class":505},[451,2481,1290],{"class":460},[451,2483,2484],{"class":457},"'Saved'",[451,2486,625],{"class":460},[451,2488,2489],{"class":457},"'success'",[451,2491,2492],{"class":460},")}>Save\u003C\u002F",[451,2494,2035],{"class":1501},[451,2496,2497],{"class":460},">;\n",[451,2499,2500],{"class":453,"line":545},[451,2501,589],{"class":460},[451,2503,2504],{"class":453,"line":561},[451,2505,468],{"emptyLinePlaceholder":467},[451,2507,2508,2511,2513,2516,2518,2521,2523,2525],{"class":453,"line":574},[451,2509,2510],{"class":505},"test",[451,2512,1290],{"class":460},[451,2514,2515],{"class":457},"'toast region has no axe violations and announces politely'",[451,2517,625],{"class":460},[451,2519,2520],{"class":474},"async",[451,2522,2161],{"class":460},[451,2524,639],{"class":474},[451,2526,542],{"class":460},[451,2528,2529,2531,2533,2536,2538,2540,2543],{"class":453,"line":586},[451,2530,757],{"class":474},[451,2532,741],{"class":460},[451,2534,2535],{"class":555},"container",[451,2537,2452],{"class":460},[451,2539,705],{"class":474},[451,2541,2542],{"class":505}," render",[451,2544,983],{"class":460},[451,2546,2547,2549,2552],{"class":453,"line":592},[451,2548,1161],{"class":460},[451,2550,2551],{"class":555},"ToastProvider",[451,2553,1214],{"class":460},[451,2555,2556,2558,2561],{"class":453,"line":597},[451,2557,1184],{"class":460},[451,2559,2560],{"class":555},"Trigger",[451,2562,2563],{"class":460}," \u002F>\n",[451,2565,2566,2568,2570],{"class":453,"line":607},[451,2567,1209],{"class":460},[451,2569,2551],{"class":555},[451,2571,2572],{"class":460},">,\n",[451,2574,2575],{"class":453,"line":647},[451,2576,1141],{"class":460},[451,2578,2579,2582,2585,2587,2589,2591,2593],{"class":453,"line":652},[451,2580,2581],{"class":474},"  await",[451,2583,2584],{"class":505}," act",[451,2586,1290],{"class":460},[451,2588,2520],{"class":474},[451,2590,2161],{"class":460},[451,2592,639],{"class":474},[451,2594,542],{"class":460},[451,2596,2597,2600,2603,2605,2608,2610,2613],{"class":453,"line":657},[451,2598,2599],{"class":460},"    screen.",[451,2601,2602],{"class":505},"getByText",[451,2604,1290],{"class":460},[451,2606,2607],{"class":457},"'Save'",[451,2609,414],{"class":460},[451,2611,2612],{"class":505},"click",[451,2614,2459],{"class":460},[451,2616,2617],{"class":453,"line":691},[451,2618,2619],{"class":460},"  });\n",[451,2621,2622,2625,2628,2631,2633,2636,2639,2642,2644,2646],{"class":453,"line":696},[451,2623,2624],{"class":505},"  expect",[451,2626,2627],{"class":460},"(screen.",[451,2629,2630],{"class":505},"getByRole",[451,2632,1290],{"class":460},[451,2634,2635],{"class":457},"'status'",[451,2637,2638],{"class":460},")).",[451,2640,2641],{"class":505},"toHaveTextContent",[451,2643,1290],{"class":460},[451,2645,2484],{"class":457},[451,2647,688],{"class":460},[451,2649,2650,2652,2654,2657,2660,2663,2666],{"class":453,"line":713},[451,2651,2624],{"class":505},[451,2653,1290],{"class":460},[451,2655,2656],{"class":474},"await",[451,2658,2659],{"class":505}," axe",[451,2661,2662],{"class":460},"(container)).",[451,2664,2665],{"class":505},"toHaveNoViolations",[451,2667,2459],{"class":460},[451,2669,2670],{"class":453,"line":718},[451,2671,2672],{"class":460},"});\n",[451,2674,2675],{"class":453,"line":754},[451,2676,468],{"emptyLinePlaceholder":467},[451,2678,2679,2681,2683,2686,2688,2690,2692,2694],{"class":453,"line":787},[451,2680,2510],{"class":505},[451,2682,1290],{"class":460},[451,2684,2685],{"class":457},"'errors render in an assertive alert region'",[451,2687,625],{"class":460},[451,2689,2520],{"class":474},[451,2691,2161],{"class":460},[451,2693,639],{"class":474},[451,2695,542],{"class":460},[451,2697,2698,2701,2704,2706,2709,2711,2714,2716],{"class":453,"line":832},[451,2699,2700],{"class":505},"  render",[451,2702,2703],{"class":460},"(\u003C",[451,2705,2551],{"class":555},[451,2707,2708],{"class":460},">\u003C",[451,2710,2560],{"class":555},[451,2712,2713],{"class":460}," \u002F>\u003C\u002F",[451,2715,2551],{"class":555},[451,2717,2718],{"class":460},">);\n",[451,2720,2721],{"class":453,"line":837},[451,2722,2723],{"class":1060},"  \u002F\u002F ...trigger an error tone, then:\n",[451,2725,2726],{"class":453,"line":866},[451,2727,2728],{"class":1060},"  \u002F\u002F expect(screen.getByRole('alert')).toHaveTextContent('Connection lost');\n",[451,2730,2731],{"class":453,"line":905},[451,2732,2672],{"class":460},[324,2734,2735,2738,2739,2741,2742,348],{},[378,2736,2737],{},"Screen reader."," Test with NVDA + Firefox, VoiceOver + Safari, and at least one mobile reader. Trigger a routine toast while idle and confirm it is spoken; trigger one while typing in a field and confirm it does ",[353,2740,423],{}," interrupt. Trigger an error and confirm it interrupts. For detailed live-region test patterns, see ",[333,2743,73],{"href":2744},"\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fscreen-reader-compatibility-testing\u002Ftesting-aria-live-regions-with-jest-and-testing-library\u002F",[324,2746,2747,2750],{},[378,2748,2749],{},"Keyboard."," Tab into a visible toast and confirm focus lands on the dismiss button with a visible focus ring; press Enter\u002FSpace to dismiss; confirm focus returns sensibly (it should not jump to the top of the page). Hover or focus the region and confirm the auto-dismiss timer pauses.",[361,2752],{},[364,2754,2756],{"id":2755},"common-a11y-mistakes","Common a11y Mistakes",[372,2758,2759,2765,2774,2788,2800,2809],{},[375,2760,2761,2764],{},[378,2762,2763],{},"Mounting the live region and its text simultaneously."," The container must pre-exist; otherwise the first toast is often silent. This is the single most common toast failure.",[375,2766,2767,2773],{},[378,2768,2769,2770,2772],{},"Using ",[328,2771,410],{}," for everything."," Assertive interruptions for routine confirmations are hostile and desensitizing. Reserve assertive for errors and time-critical warnings.",[375,2775,2776,2779,2780,2783,2784,2787],{},[378,2777,2778],{},"Stealing focus."," Calling ",[328,2781,2782],{},".focus()"," on the toast on appearance breaks ",[328,2785,2786],{},"2.4.3","-adjacent expectations and disrupts the user's task. Toasts are status messages, not dialogs.",[375,2789,2790,2793,2794,2797,2798,348],{},[378,2791,2792],{},"Icon-only dismiss buttons with no accessible name."," A bare ",[328,2795,2796],{},"×"," announces as \"button\" or nothing useful. Provide ",[328,2799,2059],{},[375,2801,2802,2805,2806,2808],{},[378,2803,2804],{},"Auto-dismissing errors, or fixed short timers with no pause."," This fails ",[328,2807,347],{},". Make errors persistent and pause routine toasts on hover\u002Ffocus.",[375,2810,2811,2817],{},[378,2812,2813,2816],{},[328,2814,2815],{},"aria-atomic=\"true\""," on the region."," It forces re-announcement of all visible toasts on every change, producing noisy, repetitive speech.",[361,2819],{},[364,2821,2823],{"id":2822},"conclusion","Conclusion",[324,2825,2826,2827,2829,2830,2832,2833,344,2835,2837],{},"Accessible toasts come down to four decisions: pre-mount the live regions, route by politeness (",[328,2828,402],{}," vs ",[328,2831,410],{},"), never move focus, and respect timing with persistent errors plus pause-on-interaction. Each maps to a concrete WCAG criterion—",[328,2834,343],{},[328,2836,347],{},"—and each is testable both automatically and by ear. Build the provider once, and every notification in your app inherits correct behavior.",[361,2839],{},[364,2841,2843],{"id":2842},"frequently-asked-questions","Frequently Asked Questions",[324,2845,2846],{},[378,2847,2848,2849,2851,2852,2854],{},"Should toast notifications use ",[328,2850,402],{}," or ",[328,2853,410],{},"?",[324,2856,1714,2857,2859,2860,2862,2863,2865],{},[328,2858,402],{}," (polite) for routine, non-urgent feedback like \"Saved\" or \"Copied to clipboard,\" so the announcement waits until the screen reader is idle and never interrupts the user. Reserve ",[328,2861,410],{}," (assertive) for errors and time-sensitive warnings the user must hear immediately. Routing every toast through ",[328,2864,410],{}," desensitizes users to genuine alerts.",[324,2867,2868],{},[378,2869,2870],{},"Why are my toasts not announced by screen readers even though the role is correct?",[324,2872,2873],{},"Almost always because the live region container is inserted into the DOM at the same moment its text appears. Screen readers register live regions when they enter the accessibility tree; a region created with content in the same render is frequently missed. Render the empty region container at app startup and only mutate its children when a toast fires.",[324,2875,2876],{},[378,2877,2878],{},"Should a toast receive keyboard focus when it appears?",[324,2880,2881],{},"No. Moving focus to a toast disrupts the user's current task and breaks expectations—a toast is a status message, not a dialog. Keep the toast itself non-focusable and provide a focusable dismiss button with an accessible name. If an action genuinely requires the user's response, use a modal dialog instead of a toast.",[324,2883,2884],{},[378,2885,2886],{},"How long should a toast stay on screen to satisfy WCAG 2.2.1?",[324,2888,2889,2890,348],{},"Routine toasts should persist long enough to read—calibrate to message length, with several seconds as a floor—and must pause when the user hovers or focuses the region. Errors and any content requiring action should not auto-dismiss at all; let the user close them. These behaviors together satisfy ",[328,2891,347],{},[361,2893],{},[364,2895,2897],{"id":2896},"related-guides","Related guides",[372,2899,2900,2904,2909,2914],{},[375,2901,2902],{},[333,2903,133],{"href":335},[375,2905,2906],{},[333,2907,151],{"href":2908},"\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Freact-context-for-global-accessibility-preferences\u002F",[375,2910,2911],{},[333,2912,145],{"href":2913},"\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Fannouncing-client-side-route-changes-in-react\u002F",[375,2915,2916],{},[333,2917,73],{"href":2744},[2919,2920,2921],"style",{},"html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":447,"searchDepth":464,"depth":464,"links":2923},[2924,2925,2926,2927,2928,2929,2930,2931,2932],{"id":366,"depth":464,"text":367},{"id":436,"depth":464,"text":437},{"id":1323,"depth":464,"text":1324},{"id":1816,"depth":464,"text":1817},{"id":2369,"depth":464,"text":2370},{"id":2755,"depth":464,"text":2756},{"id":2822,"depth":464,"text":2823},{"id":2842,"depth":464,"text":2843},{"id":2896,"depth":464,"text":2897},null,"Build toast notifications screen readers actually announce—role=status vs role=alert, aria-live politeness, focus-safe dismissal, and timing that respects slow readers.","md",{},false,{"title":139,"description":2934},"3vgV_DRK_TULnyRW2x1wOn_QeenxIRdRqd5-10t0Jcw",[2941,2980,2981,3044],{"title":5,"path":6,"stem":7,"children":2942},[2943,2944,2947,2950,2956,2962,2971,2977],{"title":10,"path":6,"stem":11},{"title":13,"path":14,"stem":15,"children":2945},[2946],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":2948},[2949],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":2951},[2952,2953],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":2954},[2955],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":2957},[2958,2959],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":2960},[2961],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":2963},[2964,2965,2968],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":2966},[2967],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":2969},[2970],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":2972},[2973,2974],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":2975},[2976],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":2978},[2979],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87},{"title":89,"path":90,"stem":91,"children":2982},[2983,2984,2990,3002,3014,3017,3026,3038],{"title":94,"path":90,"stem":95},{"title":97,"path":98,"stem":99,"children":2985},[2986,2987],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":2988},[2989],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":2991},[2992,2993,2996,2999],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":2994},[2995],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":2997},[2998],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":3000},[3001],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":3003},[3004,3005,3008,3011],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":3006},[3007],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":3009},[3010],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":3012},[3013],{"title":151,"path":152,"stem":153},{"title":157,"path":158,"stem":159,"children":3015},[3016],{"title":157,"path":158,"stem":159},{"title":163,"path":164,"stem":165,"children":3018},[3019,3020,3023],{"title":163,"path":164,"stem":165},{"title":169,"path":170,"stem":171,"children":3021},[3022],{"title":169,"path":170,"stem":171},{"title":175,"path":176,"stem":177,"children":3024},[3025],{"title":175,"path":176,"stem":177},{"title":181,"path":182,"stem":183,"children":3027},[3028,3029,3032,3035],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189,"children":3030},[3031],{"title":187,"path":188,"stem":189},{"title":193,"path":194,"stem":195,"children":3033},[3034],{"title":193,"path":194,"stem":195},{"title":199,"path":200,"stem":201,"children":3036},[3037],{"title":199,"path":200,"stem":201},{"title":205,"path":206,"stem":207,"children":3039},[3040,3041],{"title":205,"path":206,"stem":207},{"title":211,"path":212,"stem":213,"children":3042},[3043],{"title":211,"path":212,"stem":213},{"title":217,"path":218,"stem":219,"children":3045},[3046,3047,3056,3065,3074,3083],{"title":222,"path":218,"stem":223},{"title":225,"path":226,"stem":227,"children":3048},[3049,3050,3053],{"title":225,"path":226,"stem":227},{"title":231,"path":232,"stem":233,"children":3051},[3052],{"title":231,"path":232,"stem":233},{"title":237,"path":238,"stem":239,"children":3054},[3055],{"title":237,"path":238,"stem":239},{"title":243,"path":244,"stem":245,"children":3057},[3058,3059,3062],{"title":243,"path":244,"stem":245},{"title":249,"path":250,"stem":251,"children":3060},[3061],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257,"children":3063},[3064],{"title":255,"path":256,"stem":257},{"title":261,"path":262,"stem":263,"children":3066},[3067,3068,3071],{"title":261,"path":262,"stem":263},{"title":267,"path":268,"stem":269,"children":3069},[3070],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275,"children":3072},[3073],{"title":273,"path":274,"stem":275},{"title":279,"path":280,"stem":281,"children":3075},[3076,3077,3080],{"title":279,"path":280,"stem":281},{"title":285,"path":286,"stem":287,"children":3078},[3079],{"title":285,"path":286,"stem":287},{"title":291,"path":292,"stem":293,"children":3081},[3082],{"title":291,"path":292,"stem":293},{"title":297,"path":298,"stem":299,"children":3084},[3085,3086,3089],{"title":297,"path":298,"stem":299},{"title":303,"path":304,"stem":305,"children":3087},[3088],{"title":303,"path":304,"stem":305},{"title":309,"path":310,"stem":311,"children":3090},[3091],{"title":309,"path":310,"stem":311},1781785523868]