[{"data":1,"prerenderedAt":2385},["ShallowReactive",2],{"site-header-nav":3,"page-\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Fannouncing-client-side-route-changes-in-react\u002F":314,"content-navigation":2233},[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":145,"body":316,"date":2226,"description":2227,"extension":2228,"image":2226,"meta":2229,"modifiedAt":2226,"navigation":646,"noindex":2230,"path":146,"publishedAt":2226,"seo":2231,"stem":147,"updatedAt":2226,"__hash__":2232},"content\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Fannouncing-client-side-route-changes-in-react\u002Findex.md",{"type":317,"value":318,"toc":2214},"minimark",[319,323,357,360,365,368,396,399,401,405,443,616,618,622,627,1001,1004,1007,1091,1093,1097,1116,1374,1381,1383,1387,1390,1395,1666,1687,1814,1822,1824,1828,1834,2041,2052,2061,2063,2067,2115,2117,2121,2133,2135,2139,2144,2147,2152,2170,2175,2178,2183,2186,2188,2192,2211],[320,321,145],"h1",{"id":322},"announcing-client-side-route-changes-in-react",[324,325,326,327,331,332,335,336,339,340,344,345,348,349,352,353,356],"p",{},"When a user clicks a server-rendered link, the browser performs a full page load: it resets focus to the top of the document, reads the new ",[328,329,330],"code",{},"\u003Ctitle>",", and screen readers announce the new page. Single-page applications break every part of that contract. Client-side routing swaps the DOM in place, so focus stays wherever it was, the title may never change, and the screen reader says nothing at all. The user activated a link and—from their perspective—nothing happened. This guide fixes that with three coordinated pieces: an ",[328,333,334],{},"aria-live"," route announcer, focus restoration to the main heading, and a ",[328,337,338],{},"document.title"," update on every navigation. These patterns belong to the ",[341,342,133],"a",{"href":343},"\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002F"," and the broader ",[341,346,94],{"href":347},"\u002Freact-nextjs-accessibility-patterns\u002F"," set, and they satisfy WCAG ",[328,350,351],{},"4.1.3 Status Messages"," and ",[328,354,355],{},"2.4.3 Focus Order",".",[358,359],"hr",{},[361,362,364],"h2",{"id":363},"why-client-side-routing-is-silent-to-assistive-technology","Why Client-Side Routing Is Silent to Assistive Technology",[324,366,367],{},"A traditional navigation is a discrete, observable event. The browser tears down the document and builds a new one; assistive technology treats that as \"you are now on a new page\" and announces the title. A client-side route change is just a sequence of React state updates and DOM mutations. There is no page-load event, no automatic focus reset, and no implicit title announcement. Three concrete failures result:",[369,370,371,379,387],"ol",{},[372,373,374,378],"li",{},[375,376,377],"strong",{},"No announcement."," Nothing tells a screen reader user that navigation succeeded or which page they reached.",[372,380,381,384,385,356],{},[375,382,383],{},"Stranded focus."," Focus remains on the link they just activated, which may now be in a stale or removed part of the tree. Their next Tab continues from an unexpected place, violating ",[328,386,355],{},[372,388,389,392,393,395],{},[375,390,391],{},"Stale title."," Browser tab, history, and SR page identification all rely on ",[328,394,338],{},". If it never updates, every page sounds identical.",[324,397,398],{},"The fix is to manufacture the signals the browser used to provide—explicitly, on every route change.",[358,400],{},[361,402,404],{"id":403},"prerequisites","Prerequisites",[406,407,408,418,421,432],"ul",{},[372,409,410,413,414,417],{},[375,411,412],{},"React 18+",", with either React Router v6+ or Next.js App Router (",[328,415,416],{},"usePathname",").",[372,419,420],{},"A single, stable layout shell that wraps every route—this is where the announcer and focus target live.",[372,422,423,424,427,428,431],{},"A semantic landmark structure: one ",[328,425,426],{},"\u003Cmain>"," element and a single page-level ",[328,429,430],{},"\u003Ch1>"," per route. Focus restoration depends on these existing.",[372,433,434,435,438,439,442],{},"A ",[328,436,437],{},".sr-only"," utility class (visually hidden, not ",[328,440,441],{},"display:none",") for the announcer region.",[444,445,450],"pre",{"className":446,"code":447,"language":448,"meta":449,"style":449},"language-css shiki shiki-themes github-light github-dark","\u002F* Visually hidden but available to assistive technology. *\u002F\n.sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  margin: -1px;\n  padding: 0;\n  border: 0;\n  overflow: hidden;\n  clip: rect(0 0 0 0);\n  white-space: nowrap;\n}\n","css","",[328,451,452,461,471,487,504,518,533,546,558,571,597,610],{"__ignoreMap":449},[453,454,457],"span",{"class":455,"line":456},"line",1,[453,458,460],{"class":459},"sJ8bj","\u002F* Visually hidden but available to assistive technology. *\u002F\n",[453,462,464,467],{"class":455,"line":463},2,[453,465,437],{"class":466},"sScJk",[453,468,470],{"class":469},"sVt8B"," {\n",[453,472,474,478,481,484],{"class":455,"line":473},3,[453,475,477],{"class":476},"sj4cs","  position",[453,479,480],{"class":469},": ",[453,482,483],{"class":476},"absolute",[453,485,486],{"class":469},";\n",[453,488,490,493,495,498,502],{"class":455,"line":489},4,[453,491,492],{"class":476},"  width",[453,494,480],{"class":469},[453,496,497],{"class":476},"1",[453,499,501],{"class":500},"szBVR","px",[453,503,486],{"class":469},[453,505,507,510,512,514,516],{"class":455,"line":506},5,[453,508,509],{"class":476},"  height",[453,511,480],{"class":469},[453,513,497],{"class":476},[453,515,501],{"class":500},[453,517,486],{"class":469},[453,519,521,524,526,529,531],{"class":455,"line":520},6,[453,522,523],{"class":476},"  margin",[453,525,480],{"class":469},[453,527,528],{"class":476},"-1",[453,530,501],{"class":500},[453,532,486],{"class":469},[453,534,536,539,541,544],{"class":455,"line":535},7,[453,537,538],{"class":476},"  padding",[453,540,480],{"class":469},[453,542,543],{"class":476},"0",[453,545,486],{"class":469},[453,547,549,552,554,556],{"class":455,"line":548},8,[453,550,551],{"class":476},"  border",[453,553,480],{"class":469},[453,555,543],{"class":476},[453,557,486],{"class":469},[453,559,561,564,566,569],{"class":455,"line":560},9,[453,562,563],{"class":476},"  overflow",[453,565,480],{"class":469},[453,567,568],{"class":476},"hidden",[453,570,486],{"class":469},[453,572,574,577,579,582,585,587,590,592,594],{"class":455,"line":573},10,[453,575,576],{"class":476},"  clip",[453,578,480],{"class":469},[453,580,581],{"class":476},"rect",[453,583,584],{"class":469},"(",[453,586,543],{"class":476},[453,588,589],{"class":476}," 0",[453,591,589],{"class":476},[453,593,589],{"class":476},[453,595,596],{"class":469},");\n",[453,598,600,603,605,608],{"class":455,"line":599},11,[453,601,602],{"class":476},"  white-space",[453,604,480],{"class":469},[453,606,607],{"class":476},"nowrap",[453,609,486],{"class":469},[453,611,613],{"class":455,"line":612},12,[453,614,615],{"class":469},"}\n",[358,617],{},[361,619,621],{"id":620},"the-route-announcer","The Route Announcer",[324,623,624,625,356],{},"Mount one persistent polite live region in your root layout. It exists from first paint and is empty until a route change writes the new page name into it—satisfying the rule that live regions must pre-exist before they receive content, per ",[328,626,351],{},[444,628,632],{"className":629,"code":630,"language":631,"meta":449,"style":449},"language-tsx shiki shiki-themes github-light github-dark","'use client';\n\nimport { useEffect, useRef, useState } from 'react';\n\nexport function RouteAnnouncer({ pathname }: { pathname: string }) {\n  const [message, setMessage] = useState('');\n  const firstRender = useRef(true);\n\n  useEffect(() => {\n    \u002F\u002F Skip the initial mount—the browser already announced the first load.\n    if (firstRender.current) {\n      firstRender.current = false;\n      return;\n    }\n    \u002F\u002F Prefer the document title (set per route) for a human-readable label.\n    const label = document.title || pathname;\n    \u002F\u002F Clear then set on the next frame so identical paths still re-announce.\n    setMessage('');\n    const id = requestAnimationFrame(() => setMessage(`Navigated to ${label}`));\n    return () => cancelAnimationFrame(id);\n  }, [pathname]);\n\n  return (\n    \u003Cdiv role=\"status\" aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n      {message}\n    \u003C\u002Fdiv>\n  );\n}\n","tsx",[328,633,634,642,648,664,668,705,738,758,762,775,780,788,800,808,814,820,840,846,858,892,909,915,920,929,974,980,990,996],{"__ignoreMap":449},[453,635,636,640],{"class":455,"line":456},[453,637,639],{"class":638},"sZZnC","'use client'",[453,641,486],{"class":469},[453,643,644],{"class":455,"line":463},[453,645,647],{"emptyLinePlaceholder":646},true,"\n",[453,649,650,653,656,659,662],{"class":455,"line":473},[453,651,652],{"class":500},"import",[453,654,655],{"class":469}," { useEffect, useRef, useState } ",[453,657,658],{"class":500},"from",[453,660,661],{"class":638}," 'react'",[453,663,486],{"class":469},[453,665,666],{"class":455,"line":489},[453,667,647],{"emptyLinePlaceholder":646},[453,669,670,673,676,679,682,686,689,692,695,697,699,702],{"class":455,"line":506},[453,671,672],{"class":500},"export",[453,674,675],{"class":500}," function",[453,677,678],{"class":466}," RouteAnnouncer",[453,680,681],{"class":469},"({ ",[453,683,685],{"class":684},"s4XuR","pathname",[453,687,688],{"class":469}," }",[453,690,691],{"class":500},":",[453,693,694],{"class":469}," { ",[453,696,685],{"class":684},[453,698,691],{"class":500},[453,700,701],{"class":476}," string",[453,703,704],{"class":469}," }) {\n",[453,706,707,710,713,716,719,722,725,728,731,733,736],{"class":455,"line":520},[453,708,709],{"class":500},"  const",[453,711,712],{"class":469}," [",[453,714,715],{"class":476},"message",[453,717,718],{"class":469},", ",[453,720,721],{"class":476},"setMessage",[453,723,724],{"class":469},"] ",[453,726,727],{"class":500},"=",[453,729,730],{"class":466}," useState",[453,732,584],{"class":469},[453,734,735],{"class":638},"''",[453,737,596],{"class":469},[453,739,740,742,745,748,751,753,756],{"class":455,"line":535},[453,741,709],{"class":500},[453,743,744],{"class":476}," firstRender",[453,746,747],{"class":500}," =",[453,749,750],{"class":466}," useRef",[453,752,584],{"class":469},[453,754,755],{"class":476},"true",[453,757,596],{"class":469},[453,759,760],{"class":455,"line":548},[453,761,647],{"emptyLinePlaceholder":646},[453,763,764,767,770,773],{"class":455,"line":560},[453,765,766],{"class":466},"  useEffect",[453,768,769],{"class":469},"(() ",[453,771,772],{"class":500},"=>",[453,774,470],{"class":469},[453,776,777],{"class":455,"line":573},[453,778,779],{"class":459},"    \u002F\u002F Skip the initial mount—the browser already announced the first load.\n",[453,781,782,785],{"class":455,"line":599},[453,783,784],{"class":500},"    if",[453,786,787],{"class":469}," (firstRender.current) {\n",[453,789,790,793,795,798],{"class":455,"line":612},[453,791,792],{"class":469},"      firstRender.current ",[453,794,727],{"class":500},[453,796,797],{"class":476}," false",[453,799,486],{"class":469},[453,801,803,806],{"class":455,"line":802},13,[453,804,805],{"class":500},"      return",[453,807,486],{"class":469},[453,809,811],{"class":455,"line":810},14,[453,812,813],{"class":469},"    }\n",[453,815,817],{"class":455,"line":816},15,[453,818,819],{"class":459},"    \u002F\u002F Prefer the document title (set per route) for a human-readable label.\n",[453,821,823,826,829,831,834,837],{"class":455,"line":822},16,[453,824,825],{"class":500},"    const",[453,827,828],{"class":476}," label",[453,830,747],{"class":500},[453,832,833],{"class":469}," document.title ",[453,835,836],{"class":500},"||",[453,838,839],{"class":469}," pathname;\n",[453,841,843],{"class":455,"line":842},17,[453,844,845],{"class":459},"    \u002F\u002F Clear then set on the next frame so identical paths still re-announce.\n",[453,847,849,852,854,856],{"class":455,"line":848},18,[453,850,851],{"class":466},"    setMessage",[453,853,584],{"class":469},[453,855,735],{"class":638},[453,857,596],{"class":469},[453,859,861,863,866,868,871,873,875,878,880,883,886,889],{"class":455,"line":860},19,[453,862,825],{"class":500},[453,864,865],{"class":476}," id",[453,867,747],{"class":500},[453,869,870],{"class":466}," requestAnimationFrame",[453,872,769],{"class":469},[453,874,772],{"class":500},[453,876,877],{"class":466}," setMessage",[453,879,584],{"class":469},[453,881,882],{"class":638},"`Navigated to ${",[453,884,885],{"class":469},"label",[453,887,888],{"class":638},"}`",[453,890,891],{"class":469},"));\n",[453,893,895,898,901,903,906],{"class":455,"line":894},20,[453,896,897],{"class":500},"    return",[453,899,900],{"class":469}," () ",[453,902,772],{"class":500},[453,904,905],{"class":466}," cancelAnimationFrame",[453,907,908],{"class":469},"(id);\n",[453,910,912],{"class":455,"line":911},21,[453,913,914],{"class":469},"  }, [pathname]);\n",[453,916,918],{"class":455,"line":917},22,[453,919,647],{"emptyLinePlaceholder":646},[453,921,923,926],{"class":455,"line":922},23,[453,924,925],{"class":500},"  return",[453,927,928],{"class":469}," (\n",[453,930,932,935,939,942,944,947,950,952,955,958,960,963,966,968,971],{"class":455,"line":931},24,[453,933,934],{"class":469},"    \u003C",[453,936,938],{"class":937},"s9eBZ","div",[453,940,941],{"class":466}," role",[453,943,727],{"class":500},[453,945,946],{"class":638},"\"status\"",[453,948,949],{"class":466}," aria-live",[453,951,727],{"class":500},[453,953,954],{"class":638},"\"polite\"",[453,956,957],{"class":466}," aria-atomic",[453,959,727],{"class":500},[453,961,962],{"class":638},"\"true\"",[453,964,965],{"class":466}," className",[453,967,727],{"class":500},[453,969,970],{"class":638},"\"sr-only\"",[453,972,973],{"class":469},">\n",[453,975,977],{"class":455,"line":976},25,[453,978,979],{"class":469},"      {message}\n",[453,981,983,986,988],{"class":455,"line":982},26,[453,984,985],{"class":469},"    \u003C\u002F",[453,987,938],{"class":937},[453,989,973],{"class":469},[453,991,993],{"class":455,"line":992},27,[453,994,995],{"class":469},"  );\n",[453,997,999],{"class":455,"line":998},28,[453,1000,615],{"class":469},[324,1002,1003],{},"The clear-then-set on a fresh animation frame guarantees the DOM text node actually changes even when navigating between routes with the same title, which is what forces the announcement to repeat.",[324,1005,1006],{},"The sequence below shows the order of operations on a single navigation.",[1008,1009,1016,1017,1016,1021,1016,1025,1016,1035,1016,1043,1016,1048,1016,1052,1016,1055,1016,1060,1016,1064,1016,1067,1016,1071,1016,1075,1016,1079,1016,1082,1016,1085,1016,1088],"svg",{"role":1010,"ariaLabelledBy":1011,"viewBox":1014,"style":1015},"img",[1012,1013],"routeSeqT","routeSeqD","0 0 760 150","width:100%;height:auto;max-width:760px","\n  ",[1018,1019,1020],"title",{"id":1012},"Route change announcement sequence",[1022,1023,1024],"desc",{"id":1013},"A click triggers a path change, which updates the document title, moves focus to the page heading, and writes the page name into the polite live region for the screen reader to announce.",[581,1026],{"style":1027,"x":1028,"y":1029,"width":1030,"height":1031,"rx":1032,"fill":1033,"stroke":1034},"stroke-width:2","10","55","120","44","6","var(--primary-soft)","currentColor",[1036,1037,1042],"text",{"style":1038,"x":1039,"y":1040,"fill":1041},"text-anchor:middle","70","82","var(--text)","Link click",[581,1044],{"style":1027,"x":1045,"y":1029,"width":1046,"height":1031,"rx":1032,"fill":1047,"stroke":1034},"160","130","var(--surface)",[1036,1049,1051],{"style":1038,"x":1050,"y":1040,"fill":1041},"225","Path change",[581,1053],{"style":1027,"x":1054,"y":1029,"width":1046,"height":1031,"rx":1032,"fill":1047,"stroke":1034},"320",[1036,1056,1059],{"style":1038,"x":1057,"y":1058,"fill":1041},"385","76","Set title +",[1036,1061,1063],{"style":1038,"x":1057,"y":1062,"fill":1041},"92","focus h1",[581,1065],{"style":1027,"x":1066,"y":1029,"width":1046,"height":1031,"rx":1032,"fill":1047,"stroke":1034},"480",[1036,1068,1070],{"style":1038,"x":1069,"y":1040,"fill":1041},"545","Write region",[581,1072],{"style":1027,"x":1073,"y":1029,"width":1074,"height":1031,"rx":1032,"fill":1033,"stroke":1034},"640","110",[1036,1076,1078],{"style":1038,"x":1077,"y":1040,"fill":1041},"695","SR speaks",[455,1080],{"style":1027,"x1":1046,"y1":1081,"x2":1045,"y2":1081,"stroke":1034},"77",[455,1083],{"style":1027,"x1":1084,"y1":1081,"x2":1054,"y2":1081,"stroke":1034},"290",[455,1086],{"style":1027,"x1":1087,"y1":1081,"x2":1066,"y2":1081,"stroke":1034},"450",[455,1089],{"style":1027,"x1":1090,"y1":1081,"x2":1073,"y2":1081,"stroke":1034},"610",[358,1092],{},[361,1094,1096],{"id":1095},"restoring-focus-to-the-main-heading","Restoring Focus to the Main Heading",[324,1098,1099,1100,1102,1103,1105,1106,1109,1110,1112,1113,1115],{},"Announcing the page is necessary but not sufficient—focus must also move somewhere sensible so the user's next keystroke continues from the new page's start, satisfying ",[328,1101,355],{},". The conventional target is the page-level ",[328,1104,430],{},". Give it ",[328,1107,1108],{},"tabIndex={-1}"," so it is programmatically focusable without becoming a Tab stop, then focus it after each navigation. Do not move focus into ",[328,1111,426],{}," if you also use a skip link that targets ",[328,1114,426],{},"; pick one consistent destination.",[444,1117,1119],{"className":629,"code":1118,"language":631,"meta":449,"style":449},"'use client';\n\nimport { useEffect, useRef } from 'react';\n\nexport function PageHeading({ children, pathname }: { children: React.ReactNode; pathname: string }) {\n  const headingRef = useRef\u003CHTMLHeadingElement>(null);\n  const firstRender = useRef(true);\n\n  useEffect(() => {\n    if (firstRender.current) {\n      firstRender.current = false;\n      return; \u002F\u002F Leave initial focus alone on first load.\n    }\n    headingRef.current?.focus();\n  }, [pathname]);\n\n  return (\n    \u002F\u002F tabIndex={-1} makes it focusable in code but not in the Tab order.\n    \u003Ch1 ref={headingRef} tabIndex={-1} style={{ outline: 'none' }}>\n      {children}\n    \u003C\u002Fh1>\n  );\n}\n",[328,1120,1121,1127,1131,1144,1148,1195,1220,1236,1240,1250,1256,1266,1275,1279,1290,1294,1298,1304,1309,1353,1358,1366,1370],{"__ignoreMap":449},[453,1122,1123,1125],{"class":455,"line":456},[453,1124,639],{"class":638},[453,1126,486],{"class":469},[453,1128,1129],{"class":455,"line":463},[453,1130,647],{"emptyLinePlaceholder":646},[453,1132,1133,1135,1138,1140,1142],{"class":455,"line":473},[453,1134,652],{"class":500},[453,1136,1137],{"class":469}," { useEffect, useRef } ",[453,1139,658],{"class":500},[453,1141,661],{"class":638},[453,1143,486],{"class":469},[453,1145,1146],{"class":455,"line":489},[453,1147,647],{"emptyLinePlaceholder":646},[453,1149,1150,1152,1154,1157,1159,1162,1164,1166,1168,1170,1172,1174,1176,1179,1181,1184,1187,1189,1191,1193],{"class":455,"line":506},[453,1151,672],{"class":500},[453,1153,675],{"class":500},[453,1155,1156],{"class":466}," PageHeading",[453,1158,681],{"class":469},[453,1160,1161],{"class":684},"children",[453,1163,718],{"class":469},[453,1165,685],{"class":684},[453,1167,688],{"class":469},[453,1169,691],{"class":500},[453,1171,694],{"class":469},[453,1173,1161],{"class":684},[453,1175,691],{"class":500},[453,1177,1178],{"class":466}," React",[453,1180,356],{"class":469},[453,1182,1183],{"class":466},"ReactNode",[453,1185,1186],{"class":469},"; ",[453,1188,685],{"class":684},[453,1190,691],{"class":500},[453,1192,701],{"class":476},[453,1194,704],{"class":469},[453,1196,1197,1199,1202,1204,1206,1209,1212,1215,1218],{"class":455,"line":520},[453,1198,709],{"class":500},[453,1200,1201],{"class":476}," headingRef",[453,1203,747],{"class":500},[453,1205,750],{"class":466},[453,1207,1208],{"class":469},"\u003C",[453,1210,1211],{"class":466},"HTMLHeadingElement",[453,1213,1214],{"class":469},">(",[453,1216,1217],{"class":476},"null",[453,1219,596],{"class":469},[453,1221,1222,1224,1226,1228,1230,1232,1234],{"class":455,"line":535},[453,1223,709],{"class":500},[453,1225,744],{"class":476},[453,1227,747],{"class":500},[453,1229,750],{"class":466},[453,1231,584],{"class":469},[453,1233,755],{"class":476},[453,1235,596],{"class":469},[453,1237,1238],{"class":455,"line":548},[453,1239,647],{"emptyLinePlaceholder":646},[453,1241,1242,1244,1246,1248],{"class":455,"line":560},[453,1243,766],{"class":466},[453,1245,769],{"class":469},[453,1247,772],{"class":500},[453,1249,470],{"class":469},[453,1251,1252,1254],{"class":455,"line":573},[453,1253,784],{"class":500},[453,1255,787],{"class":469},[453,1257,1258,1260,1262,1264],{"class":455,"line":599},[453,1259,792],{"class":469},[453,1261,727],{"class":500},[453,1263,797],{"class":476},[453,1265,486],{"class":469},[453,1267,1268,1270,1272],{"class":455,"line":612},[453,1269,805],{"class":500},[453,1271,1186],{"class":469},[453,1273,1274],{"class":459},"\u002F\u002F Leave initial focus alone on first load.\n",[453,1276,1277],{"class":455,"line":802},[453,1278,813],{"class":469},[453,1280,1281,1284,1287],{"class":455,"line":810},[453,1282,1283],{"class":469},"    headingRef.current?.",[453,1285,1286],{"class":466},"focus",[453,1288,1289],{"class":469},"();\n",[453,1291,1292],{"class":455,"line":816},[453,1293,914],{"class":469},[453,1295,1296],{"class":455,"line":822},[453,1297,647],{"emptyLinePlaceholder":646},[453,1299,1300,1302],{"class":455,"line":842},[453,1301,925],{"class":500},[453,1303,928],{"class":469},[453,1305,1306],{"class":455,"line":848},[453,1307,1308],{"class":459},"    \u002F\u002F tabIndex={-1} makes it focusable in code but not in the Tab order.\n",[453,1310,1311,1313,1315,1318,1320,1323,1326,1328,1331,1334,1336,1339,1342,1344,1347,1350],{"class":455,"line":860},[453,1312,934],{"class":469},[453,1314,320],{"class":937},[453,1316,1317],{"class":466}," ref",[453,1319,727],{"class":500},[453,1321,1322],{"class":469},"{headingRef} ",[453,1324,1325],{"class":466},"tabIndex",[453,1327,727],{"class":500},[453,1329,1330],{"class":469},"{",[453,1332,1333],{"class":500},"-",[453,1335,497],{"class":476},[453,1337,1338],{"class":469},"} ",[453,1340,1341],{"class":466},"style",[453,1343,727],{"class":500},[453,1345,1346],{"class":469},"{{ outline: ",[453,1348,1349],{"class":638},"'none'",[453,1351,1352],{"class":469}," }}>\n",[453,1354,1355],{"class":455,"line":894},[453,1356,1357],{"class":469},"      {children}\n",[453,1359,1360,1362,1364],{"class":455,"line":911},[453,1361,985],{"class":469},[453,1363,320],{"class":937},[453,1365,973],{"class":469},[453,1367,1368],{"class":455,"line":917},[453,1369,995],{"class":469},[453,1371,1372],{"class":455,"line":922},[453,1373,615],{"class":469},[324,1375,1376,1377,1380],{},"Removing the default focus outline on the heading is acceptable here because the heading is not a Tab stop—the user did not Tab to it. If your design benefits from a visible cue when focus lands, keep a subtle outline instead of removing it. Cross-reference the deeper treatment in ",[341,1378,31],{"href":1379},"\u002Fcore-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas\u002Fhandling-focus-restoration-after-dynamic-route-changes\u002F"," for edge cases like scroll restoration and nested layouts.",[358,1382],{},[361,1384,1386],{"id":1385},"wiring-it-up-react-router-and-nextjs","Wiring It Up: React Router and Next.js",[324,1388,1389],{},"Both routers expose the current location reactively. The integration is the same shape—observe the path, set the title, render the announcer and heading.",[324,1391,1392],{},[375,1393,1394],{},"React Router (v6+):",[444,1396,1398],{"className":629,"code":1397,"language":631,"meta":449,"style":449},"'use client';\n\nimport { useLocation } from 'react-router-dom';\nimport { useEffect } from 'react';\nimport { RouteAnnouncer } from '.\u002FRouteAnnouncer';\n\nconst TITLES: Record\u003Cstring, string> = {\n  '\u002F': 'Home — Acme',\n  '\u002Fbilling': 'Billing — Acme',\n};\n\nexport function AppShell({ children }: { children: React.ReactNode }) {\n  const { pathname } = useLocation();\n\n  useEffect(() => {\n    document.title = TITLES[pathname] ?? 'Acme'; \u002F\u002F Update title first.\n  }, [pathname]);\n\n  return (\n    \u003C>\n      \u003CRouteAnnouncer pathname={pathname} \u002F>\n      \u003Cmain>{children}\u003C\u002Fmain>\n    \u003C\u002F>\n  );\n}\n",[328,1399,1400,1406,1410,1424,1437,1451,1455,1484,1497,1509,1514,1518,1549,1567,1571,1581,1604,1608,1612,1618,1623,1639,1653,1658,1662],{"__ignoreMap":449},[453,1401,1402,1404],{"class":455,"line":456},[453,1403,639],{"class":638},[453,1405,486],{"class":469},[453,1407,1408],{"class":455,"line":463},[453,1409,647],{"emptyLinePlaceholder":646},[453,1411,1412,1414,1417,1419,1422],{"class":455,"line":473},[453,1413,652],{"class":500},[453,1415,1416],{"class":469}," { useLocation } ",[453,1418,658],{"class":500},[453,1420,1421],{"class":638}," 'react-router-dom'",[453,1423,486],{"class":469},[453,1425,1426,1428,1431,1433,1435],{"class":455,"line":489},[453,1427,652],{"class":500},[453,1429,1430],{"class":469}," { useEffect } ",[453,1432,658],{"class":500},[453,1434,661],{"class":638},[453,1436,486],{"class":469},[453,1438,1439,1441,1444,1446,1449],{"class":455,"line":506},[453,1440,652],{"class":500},[453,1442,1443],{"class":469}," { RouteAnnouncer } ",[453,1445,658],{"class":500},[453,1447,1448],{"class":638}," '.\u002FRouteAnnouncer'",[453,1450,486],{"class":469},[453,1452,1453],{"class":455,"line":520},[453,1454,647],{"emptyLinePlaceholder":646},[453,1456,1457,1460,1463,1465,1468,1470,1473,1475,1477,1480,1482],{"class":455,"line":535},[453,1458,1459],{"class":500},"const",[453,1461,1462],{"class":476}," TITLES",[453,1464,691],{"class":500},[453,1466,1467],{"class":466}," Record",[453,1469,1208],{"class":469},[453,1471,1472],{"class":476},"string",[453,1474,718],{"class":469},[453,1476,1472],{"class":476},[453,1478,1479],{"class":469},"> ",[453,1481,727],{"class":500},[453,1483,470],{"class":469},[453,1485,1486,1489,1491,1494],{"class":455,"line":548},[453,1487,1488],{"class":638},"  '\u002F'",[453,1490,480],{"class":469},[453,1492,1493],{"class":638},"'Home — Acme'",[453,1495,1496],{"class":469},",\n",[453,1498,1499,1502,1504,1507],{"class":455,"line":560},[453,1500,1501],{"class":638},"  '\u002Fbilling'",[453,1503,480],{"class":469},[453,1505,1506],{"class":638},"'Billing — Acme'",[453,1508,1496],{"class":469},[453,1510,1511],{"class":455,"line":573},[453,1512,1513],{"class":469},"};\n",[453,1515,1516],{"class":455,"line":599},[453,1517,647],{"emptyLinePlaceholder":646},[453,1519,1520,1522,1524,1527,1529,1531,1533,1535,1537,1539,1541,1543,1545,1547],{"class":455,"line":612},[453,1521,672],{"class":500},[453,1523,675],{"class":500},[453,1525,1526],{"class":466}," AppShell",[453,1528,681],{"class":469},[453,1530,1161],{"class":684},[453,1532,688],{"class":469},[453,1534,691],{"class":500},[453,1536,694],{"class":469},[453,1538,1161],{"class":684},[453,1540,691],{"class":500},[453,1542,1178],{"class":466},[453,1544,356],{"class":469},[453,1546,1183],{"class":466},[453,1548,704],{"class":469},[453,1550,1551,1553,1555,1557,1560,1562,1565],{"class":455,"line":802},[453,1552,709],{"class":500},[453,1554,694],{"class":469},[453,1556,685],{"class":476},[453,1558,1559],{"class":469}," } ",[453,1561,727],{"class":500},[453,1563,1564],{"class":466}," useLocation",[453,1566,1289],{"class":469},[453,1568,1569],{"class":455,"line":810},[453,1570,647],{"emptyLinePlaceholder":646},[453,1572,1573,1575,1577,1579],{"class":455,"line":816},[453,1574,766],{"class":466},[453,1576,769],{"class":469},[453,1578,772],{"class":500},[453,1580,470],{"class":469},[453,1582,1583,1586,1588,1590,1593,1596,1599,1601],{"class":455,"line":822},[453,1584,1585],{"class":469},"    document.title ",[453,1587,727],{"class":500},[453,1589,1462],{"class":476},[453,1591,1592],{"class":469},"[pathname] ",[453,1594,1595],{"class":500},"??",[453,1597,1598],{"class":638}," 'Acme'",[453,1600,1186],{"class":469},[453,1602,1603],{"class":459},"\u002F\u002F Update title first.\n",[453,1605,1606],{"class":455,"line":842},[453,1607,914],{"class":469},[453,1609,1610],{"class":455,"line":848},[453,1611,647],{"emptyLinePlaceholder":646},[453,1613,1614,1616],{"class":455,"line":860},[453,1615,925],{"class":500},[453,1617,928],{"class":469},[453,1619,1620],{"class":455,"line":894},[453,1621,1622],{"class":469},"    \u003C>\n",[453,1624,1625,1628,1631,1634,1636],{"class":455,"line":911},[453,1626,1627],{"class":469},"      \u003C",[453,1629,1630],{"class":476},"RouteAnnouncer",[453,1632,1633],{"class":466}," pathname",[453,1635,727],{"class":500},[453,1637,1638],{"class":469},"{pathname} \u002F>\n",[453,1640,1641,1643,1646,1649,1651],{"class":455,"line":917},[453,1642,1627],{"class":469},[453,1644,1645],{"class":937},"main",[453,1647,1648],{"class":469},">{children}\u003C\u002F",[453,1650,1645],{"class":937},[453,1652,973],{"class":469},[453,1654,1655],{"class":455,"line":922},[453,1656,1657],{"class":469},"    \u003C\u002F>\n",[453,1659,1660],{"class":455,"line":931},[453,1661,995],{"class":469},[453,1663,1664],{"class":455,"line":976},[453,1665,615],{"class":469},[324,1667,1668,1671,1672,1675,1676,1679,1680,1683,1684,1686],{},[375,1669,1670],{},"Next.js App Router"," uses ",[328,1673,1674],{},"usePathname()",". Per-route titles are best set with the framework's Metadata API (",[328,1677,1678],{},"export const metadata"," or ",[328,1681,1682],{},"generateMetadata","), which updates ",[328,1685,338],{}," for you; the announcer then reads that title:",[444,1688,1690],{"className":629,"code":1689,"language":631,"meta":449,"style":449},"'use client';\n\nimport { usePathname } from 'next\u002Fnavigation';\nimport { RouteAnnouncer } from '.\u002FRouteAnnouncer';\n\nexport function RootClientShell({ children }: { children: React.ReactNode }) {\n  const pathname = usePathname();\n  return (\n    \u003C>\n      \u003CRouteAnnouncer pathname={pathname} \u002F>\n      {children}\n    \u003C\u002F>\n  );\n}\n",[328,1691,1692,1698,1702,1716,1728,1732,1763,1776,1782,1786,1798,1802,1806,1810],{"__ignoreMap":449},[453,1693,1694,1696],{"class":455,"line":456},[453,1695,639],{"class":638},[453,1697,486],{"class":469},[453,1699,1700],{"class":455,"line":463},[453,1701,647],{"emptyLinePlaceholder":646},[453,1703,1704,1706,1709,1711,1714],{"class":455,"line":473},[453,1705,652],{"class":500},[453,1707,1708],{"class":469}," { usePathname } ",[453,1710,658],{"class":500},[453,1712,1713],{"class":638}," 'next\u002Fnavigation'",[453,1715,486],{"class":469},[453,1717,1718,1720,1722,1724,1726],{"class":455,"line":489},[453,1719,652],{"class":500},[453,1721,1443],{"class":469},[453,1723,658],{"class":500},[453,1725,1448],{"class":638},[453,1727,486],{"class":469},[453,1729,1730],{"class":455,"line":506},[453,1731,647],{"emptyLinePlaceholder":646},[453,1733,1734,1736,1738,1741,1743,1745,1747,1749,1751,1753,1755,1757,1759,1761],{"class":455,"line":520},[453,1735,672],{"class":500},[453,1737,675],{"class":500},[453,1739,1740],{"class":466}," RootClientShell",[453,1742,681],{"class":469},[453,1744,1161],{"class":684},[453,1746,688],{"class":469},[453,1748,691],{"class":500},[453,1750,694],{"class":469},[453,1752,1161],{"class":684},[453,1754,691],{"class":500},[453,1756,1178],{"class":466},[453,1758,356],{"class":469},[453,1760,1183],{"class":466},[453,1762,704],{"class":469},[453,1764,1765,1767,1769,1771,1774],{"class":455,"line":535},[453,1766,709],{"class":500},[453,1768,1633],{"class":476},[453,1770,747],{"class":500},[453,1772,1773],{"class":466}," usePathname",[453,1775,1289],{"class":469},[453,1777,1778,1780],{"class":455,"line":548},[453,1779,925],{"class":500},[453,1781,928],{"class":469},[453,1783,1784],{"class":455,"line":560},[453,1785,1622],{"class":469},[453,1787,1788,1790,1792,1794,1796],{"class":455,"line":573},[453,1789,1627],{"class":469},[453,1791,1630],{"class":476},[453,1793,1633],{"class":466},[453,1795,727],{"class":500},[453,1797,1638],{"class":469},[453,1799,1800],{"class":455,"line":599},[453,1801,1357],{"class":469},[453,1803,1804],{"class":455,"line":612},[453,1805,1657],{"class":469},[453,1807,1808],{"class":455,"line":802},[453,1809,995],{"class":469},[453,1811,1812],{"class":455,"line":810},[453,1813,615],{"class":469},[324,1815,1816,1817,1821],{},"Note that Next.js ships its own built-in route announcer in the App Router. If you add your own, verify in a screen reader that you are not double-announcing; prefer relying on the framework's announcer for the page name and reserving any custom region for additional context. Pair this with a skip link as described in ",[341,1818,1820],{"href":1819},"\u002Freact-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Fimplementing-skip-links-in-nextjs-app-router\u002F","Implementing Skip Links in Next.js App Router"," so keyboard users can bypass repeated navigation after each route change.",[358,1823],{},[361,1825,1827],{"id":1826},"how-to-verify","How to Verify",[324,1829,1830,1833],{},[375,1831,1832],{},"axe \u002F jest-axe."," Assert the announcer renders a polite status region and that text updates after navigation:",[444,1835,1837],{"className":629,"code":1836,"language":631,"meta":449,"style":449},"import { render, screen, act } from '@testing-library\u002Freact';\nimport { axe } from 'jest-axe';\nimport { RouteAnnouncer } from '.\u002FRouteAnnouncer';\n\ntest('announcer updates and has no violations', async () => {\n  const { container, rerender } = render(\u003CRouteAnnouncer pathname=\"\u002F\" \u002F>);\n  document.title = 'Billing — Acme';\n  await act(async () => {\n    rerender(\u003CRouteAnnouncer pathname=\"\u002Fbilling\" \u002F>);\n  });\n  await screen.findByText(\u002FNavigated to Billing\u002F);\n  expect(await axe(container)).toHaveNoViolations();\n});\n",[328,1838,1839,1853,1867,1879,1883,1904,1940,1952,1970,1988,1993,2015,2036],{"__ignoreMap":449},[453,1840,1841,1843,1846,1848,1851],{"class":455,"line":456},[453,1842,652],{"class":500},[453,1844,1845],{"class":469}," { render, screen, act } ",[453,1847,658],{"class":500},[453,1849,1850],{"class":638}," '@testing-library\u002Freact'",[453,1852,486],{"class":469},[453,1854,1855,1857,1860,1862,1865],{"class":455,"line":463},[453,1856,652],{"class":500},[453,1858,1859],{"class":469}," { axe } ",[453,1861,658],{"class":500},[453,1863,1864],{"class":638}," 'jest-axe'",[453,1866,486],{"class":469},[453,1868,1869,1871,1873,1875,1877],{"class":455,"line":473},[453,1870,652],{"class":500},[453,1872,1443],{"class":469},[453,1874,658],{"class":500},[453,1876,1448],{"class":638},[453,1878,486],{"class":469},[453,1880,1881],{"class":455,"line":489},[453,1882,647],{"emptyLinePlaceholder":646},[453,1884,1885,1888,1890,1893,1895,1898,1900,1902],{"class":455,"line":506},[453,1886,1887],{"class":466},"test",[453,1889,584],{"class":469},[453,1891,1892],{"class":638},"'announcer updates and has no violations'",[453,1894,718],{"class":469},[453,1896,1897],{"class":500},"async",[453,1899,900],{"class":469},[453,1901,772],{"class":500},[453,1903,470],{"class":469},[453,1905,1906,1908,1910,1913,1915,1918,1920,1922,1925,1928,1930,1932,1934,1937],{"class":455,"line":520},[453,1907,709],{"class":500},[453,1909,694],{"class":469},[453,1911,1912],{"class":476},"container",[453,1914,718],{"class":469},[453,1916,1917],{"class":476},"rerender",[453,1919,1559],{"class":469},[453,1921,727],{"class":500},[453,1923,1924],{"class":466}," render",[453,1926,1927],{"class":469},"(\u003C",[453,1929,1630],{"class":476},[453,1931,1633],{"class":466},[453,1933,727],{"class":500},[453,1935,1936],{"class":638},"\"\u002F\"",[453,1938,1939],{"class":469}," \u002F>);\n",[453,1941,1942,1945,1947,1950],{"class":455,"line":535},[453,1943,1944],{"class":469},"  document.title ",[453,1946,727],{"class":500},[453,1948,1949],{"class":638}," 'Billing — Acme'",[453,1951,486],{"class":469},[453,1953,1954,1957,1960,1962,1964,1966,1968],{"class":455,"line":548},[453,1955,1956],{"class":500},"  await",[453,1958,1959],{"class":466}," act",[453,1961,584],{"class":469},[453,1963,1897],{"class":500},[453,1965,900],{"class":469},[453,1967,772],{"class":500},[453,1969,470],{"class":469},[453,1971,1972,1975,1977,1979,1981,1983,1986],{"class":455,"line":560},[453,1973,1974],{"class":466},"    rerender",[453,1976,1927],{"class":469},[453,1978,1630],{"class":476},[453,1980,1633],{"class":466},[453,1982,727],{"class":500},[453,1984,1985],{"class":638},"\"\u002Fbilling\"",[453,1987,1939],{"class":469},[453,1989,1990],{"class":455,"line":573},[453,1991,1992],{"class":469},"  });\n",[453,1994,1995,1997,2000,2003,2005,2007,2011,2013],{"class":455,"line":599},[453,1996,1956],{"class":500},[453,1998,1999],{"class":469}," screen.",[453,2001,2002],{"class":466},"findByText",[453,2004,584],{"class":469},[453,2006,86],{"class":638},[453,2008,2010],{"class":2009},"sA_wV","Navigated to Billing",[453,2012,86],{"class":638},[453,2014,596],{"class":469},[453,2016,2017,2020,2022,2025,2028,2031,2034],{"class":455,"line":612},[453,2018,2019],{"class":466},"  expect",[453,2021,584],{"class":469},[453,2023,2024],{"class":500},"await",[453,2026,2027],{"class":466}," axe",[453,2029,2030],{"class":469},"(container)).",[453,2032,2033],{"class":466},"toHaveNoViolations",[453,2035,1289],{"class":469},[453,2037,2038],{"class":455,"line":802},[453,2039,2040],{"class":469},"});\n",[324,2042,2043,2046,2047,2051],{},[375,2044,2045],{},"Screen reader."," With NVDA + Firefox and VoiceOver + Safari, activate in-app links and confirm each navigation is announced with the destination page name. Confirm the first page load is ",[2048,2049,2050],"em",{},"not"," double-announced. Navigate between two pages that share a title and confirm it still re-announces.",[324,2053,2054,2057,2058,2060],{},[375,2055,2056],{},"Keyboard."," Tab to a link, activate it, then press Tab again and confirm focus continues from the new page's heading region—not from a stale position. Confirm the ",[328,2059,430],{}," receives focus without becoming a redundant Tab stop on subsequent tabbing.",[358,2062],{},[361,2064,2066],{"id":2065},"common-a11y-mistakes","Common a11y Mistakes",[406,2068,2069,2075,2081,2087,2096,2109],{},[372,2070,2071,2074],{},[375,2072,2073],{},"No live region at all."," Relying on the title change alone is unreliable across AT; mount an explicit polite region.",[372,2076,2077,2080],{},[375,2078,2079],{},"Creating the region on navigation instead of mounting it persistently."," Late-inserted live regions are frequently missed; render it empty at app start.",[372,2082,2083,2086],{},[375,2084,2085],{},"Announcing on first load."," Double-announcing the initial page is noisy. Skip the first effect run.",[372,2088,2089,2092,2093,2095],{},[375,2090,2091],{},"Forgetting focus restoration."," Announcing the page but leaving focus on the clicked link fails ",[328,2094,355],{}," and strands keyboard users.",[372,2097,2098,2105,2106,356],{},[375,2099,2100,2101,2104],{},"Using ",[328,2102,2103],{},"assertive"," for routine navigation."," Route changes are not emergencies—use ",[328,2107,2108],{},"aria-live=\"polite\"",[372,2110,2111,2114],{},[375,2112,2113],{},"Double announcers in Next.js."," Adding a custom announcer on top of the framework's built-in one produces stuttering, repeated speech.",[358,2116],{},[361,2118,2120],{"id":2119},"conclusion","Conclusion",[324,2122,2123,2124,2126,2127,2129,2130,2132],{},"SPA navigation only feels broken to screen reader and keyboard users because the framework removed the browser's built-in cues without replacing them. Restore all three—a persistent polite announcer (",[328,2125,351],{},"), focus moved to the page heading (",[328,2128,355],{},"), and a fresh ",[328,2131,338],{},"—and client-side routing becomes as legible to assistive technology as a full page load, while keeping the speed of an SPA.",[358,2134],{},[361,2136,2138],{"id":2137},"frequently-asked-questions","Frequently Asked Questions",[324,2140,2141],{},[375,2142,2143],{},"Why don't screen readers announce route changes in a React SPA automatically?",[324,2145,2146],{},"Because there is no page-load event. A full navigation tears down and rebuilds the document, which assistive technology treats as a new page and announces. Client-side routing only mutates the existing DOM, so there is no load event, no automatic focus reset, and no implied title announcement. You must manufacture these signals yourself with a live region, focus management, and a title update.",[324,2148,2149],{},[375,2150,2151],{},"Should I move focus to the main element or the h1 after navigation?",[324,2153,2154,2155,2157,2158,2160,2161,2163,2164,2166,2167,2169],{},"Move it to the page-level ",[328,2156,430],{}," with ",[328,2159,1108],{},", or consistently to ",[328,2162,426],{},"—but pick one and apply it everywhere. The heading is usually the better choice because it reads the page name as focus lands. Avoid moving focus to ",[328,2165,426],{}," if your skip link already targets ",[328,2168,426],{},", to prevent conflicting destinations.",[324,2171,2172],{},[375,2173,2174],{},"Do I need a custom route announcer in Next.js App Router?",[324,2176,2177],{},"Often not—Next.js ships a built-in route announcer in the App Router that reads the page title on navigation. Set per-route titles with the Metadata API and verify in a real screen reader. Only add a custom region if you need to announce extra context, and confirm you are not double-announcing the page name.",[324,2179,2180],{},[375,2181,2182],{},"How do I make navigation between two pages with the same title still announce?",[324,2184,2185],{},"Clear the live region's text and re-set it on a fresh animation frame (or after a microtask). Because the text node value changes from empty back to a string, assistive technology detects a mutation and announces again, even when the destination title is identical to the previous one.",[358,2187],{},[361,2189,2191],{"id":2190},"related-guides","Related guides",[406,2193,2194,2198,2202,2206],{},[372,2195,2196],{},[341,2197,133],{"href":343},[372,2199,2200],{},[341,2201,1820],{"href":1819},[372,2203,2204],{},[341,2205,31],{"href":1379},[372,2207,2208],{},[341,2209,139],{"href":2210},"\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Faccessible-toast-notifications-in-react\u002F",[1341,2212,2213],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}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 .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}",{"title":449,"searchDepth":463,"depth":463,"links":2215},[2216,2217,2218,2219,2220,2221,2222,2223,2224,2225],{"id":363,"depth":463,"text":364},{"id":403,"depth":463,"text":404},{"id":620,"depth":463,"text":621},{"id":1095,"depth":463,"text":1096},{"id":1385,"depth":463,"text":1386},{"id":1826,"depth":463,"text":1827},{"id":2065,"depth":463,"text":2066},{"id":2119,"depth":463,"text":2120},{"id":2137,"depth":463,"text":2138},{"id":2190,"depth":463,"text":2191},null,"Stop SPA navigation from leaving screen reader users lost—add an aria-live route announcer, restore focus to the main heading, and update document.title on every route change.","md",{},false,{"title":145,"description":2227},"VLy_YqsvrhZf0dSCpNsFEBnxF2u-6SMnGaIdPfT4z84",[2234,2273,2274,2337],{"title":5,"path":6,"stem":7,"children":2235},[2236,2237,2240,2243,2249,2255,2264,2270],{"title":10,"path":6,"stem":11},{"title":13,"path":14,"stem":15,"children":2238},[2239],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":2241},[2242],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":2244},[2245,2246],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":2247},[2248],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":2250},[2251,2252],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":2253},[2254],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":2256},[2257,2258,2261],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":2259},[2260],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":2262},[2263],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":2265},[2266,2267],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":2268},[2269],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":2271},[2272],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87},{"title":89,"path":90,"stem":91,"children":2275},[2276,2277,2283,2295,2307,2310,2319,2331],{"title":94,"path":90,"stem":95},{"title":97,"path":98,"stem":99,"children":2278},[2279,2280],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":2281},[2282],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":2284},[2285,2286,2289,2292],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":2287},[2288],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":2290},[2291],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":2293},[2294],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":2296},[2297,2298,2301,2304],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":2299},[2300],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":2302},[2303],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":2305},[2306],{"title":151,"path":152,"stem":153},{"title":157,"path":158,"stem":159,"children":2308},[2309],{"title":157,"path":158,"stem":159},{"title":163,"path":164,"stem":165,"children":2311},[2312,2313,2316],{"title":163,"path":164,"stem":165},{"title":169,"path":170,"stem":171,"children":2314},[2315],{"title":169,"path":170,"stem":171},{"title":175,"path":176,"stem":177,"children":2317},[2318],{"title":175,"path":176,"stem":177},{"title":181,"path":182,"stem":183,"children":2320},[2321,2322,2325,2328],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189,"children":2323},[2324],{"title":187,"path":188,"stem":189},{"title":193,"path":194,"stem":195,"children":2326},[2327],{"title":193,"path":194,"stem":195},{"title":199,"path":200,"stem":201,"children":2329},[2330],{"title":199,"path":200,"stem":201},{"title":205,"path":206,"stem":207,"children":2332},[2333,2334],{"title":205,"path":206,"stem":207},{"title":211,"path":212,"stem":213,"children":2335},[2336],{"title":211,"path":212,"stem":213},{"title":217,"path":218,"stem":219,"children":2338},[2339,2340,2349,2358,2367,2376],{"title":222,"path":218,"stem":223},{"title":225,"path":226,"stem":227,"children":2341},[2342,2343,2346],{"title":225,"path":226,"stem":227},{"title":231,"path":232,"stem":233,"children":2344},[2345],{"title":231,"path":232,"stem":233},{"title":237,"path":238,"stem":239,"children":2347},[2348],{"title":237,"path":238,"stem":239},{"title":243,"path":244,"stem":245,"children":2350},[2351,2352,2355],{"title":243,"path":244,"stem":245},{"title":249,"path":250,"stem":251,"children":2353},[2354],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257,"children":2356},[2357],{"title":255,"path":256,"stem":257},{"title":261,"path":262,"stem":263,"children":2359},[2360,2361,2364],{"title":261,"path":262,"stem":263},{"title":267,"path":268,"stem":269,"children":2362},[2363],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275,"children":2365},[2366],{"title":273,"path":274,"stem":275},{"title":279,"path":280,"stem":281,"children":2368},[2369,2370,2373],{"title":279,"path":280,"stem":281},{"title":285,"path":286,"stem":287,"children":2371},[2372],{"title":285,"path":286,"stem":287},{"title":291,"path":292,"stem":293,"children":2374},[2375],{"title":291,"path":292,"stem":293},{"title":297,"path":298,"stem":299,"children":2377},[2378,2379,2382],{"title":297,"path":298,"stem":299},{"title":303,"path":304,"stem":305,"children":2380},[2381],{"title":303,"path":304,"stem":305},{"title":309,"path":310,"stem":311,"children":2383},[2384],{"title":309,"path":310,"stem":311},1781785523576]