[{"data":1,"prerenderedAt":3107},["ShallowReactive",2],{"site-header-nav":3,"page-\u002Freact-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002F":314,"content-navigation":2955},[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":163,"body":316,"date":2948,"description":2949,"extension":2950,"image":2948,"meta":2951,"modifiedAt":2948,"navigation":704,"noindex":2952,"path":164,"publishedAt":2948,"seo":2953,"stem":165,"updatedAt":2948,"__hash__":2954},"content\u002Freact-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Findex.md",{"type":317,"value":318,"toc":2935},"minimark",[319,324,333,336,342,377,382,393,396,401,412,415,418,600,608,610,614,617,637,901,914,917,1240,1253,1255,1259,1265,1280,1303,1513,2101,2120,2134,2136,2140,2147,2176,2188,2201,2203,2207,2210,2231,2351,2365,2370,2372,2376,2384,2395,2663,2673,2681,2683,2687,2690,2742,2745,2747,2751,2804,2806,2810,2830,2832,2836,2847,2859,2872,2884,2897,2906,2908,2912,2931],[320,321,323],"h1",{"id":322},"nextjs-app-router-a11y-implementation-guide-for-modern-frameworks","Next.js App Router & A11y: Implementation Guide for Modern Frameworks",[325,326,327,328,332],"p",{},"The Next.js App Router introduces a paradigm shift in routing and rendering that directly impacts accessibility implementation. This guide bridges foundational ",[329,330,94],"a",{"href":331},"\u002Freact-nextjs-accessibility-patterns\u002F"," with App Router-specific constraints, focusing on routing transitions, focus management, and hybrid rendering strategies.",[325,334,335],{},"The core difficulty is that the App Router blurs the boundary between server and client. Markup arrives partly as serialized React Server Component (RSC) payload streamed over the wire, and partly as hydrated client islands. Assistive technology, however, only ever sees the final accessibility tree the browser computes from that combined output. When your mental model treats the page as a single client-rendered SPA, you will misplace focus hooks, duplicate landmarks, and announce route changes inconsistently. This guide makes the server\u002Fclient seam explicit so that every accessibility decision lands on the correct side of it.",[325,337,338],{},[339,340,341],"strong",{},"Mapped WCAG Success Criteria:",[343,344,345,353,359,365,371],"ul",{},[346,347,348,352],"li",{},[349,350,351],"code",{},"2.1.1 Keyboard",": Ensuring all interactive elements remain operable without a mouse during route transitions and streaming.",[346,354,355,358],{},[349,356,357],{},"2.4.3 Focus Order",": Maintaining logical focus progression across parallel routes and layout boundaries.",[346,360,361,364],{},[349,362,363],{},"4.1.2 Name, Role, Value",": Preserving accurate ARIA semantics when React Server Components (RSCs) serialize static markup.",[346,366,367,370],{},[349,368,369],{},"3.2.1 On Input",": Preventing unexpected context shifts when intercepting routes or dynamic imports resolve.",[346,372,373,376],{},[349,374,375],{},"4.1.3 Status Messages",": Announcing navigation, loading, and streaming completion through live regions rather than relying on visual cues.",[325,378,379],{},[339,380,381],{},"Key Architectural Considerations:",[343,383,384,387,390],{},[346,385,386],{},"App Router's parallel and intercepting routes require explicit focus trapping and announcement strategies, as they render independent UI trees.",[346,388,389],{},"Server Components strip client-side interactivity, necessitating careful boundary placement for accessibility hooks.",[346,391,392],{},"Route transitions bypass traditional SPA navigation, requiring manual focus management to meet WCAG 2.4.3.",[394,395],"hr",{},[397,398,400],"h2",{"id":399},"how-app-router-navigation-affects-assistive-technology","How App Router Navigation Affects Assistive Technology",[325,402,403,404,407,408,411],{},"When a user clicks a ",[349,405,406],{},"next\u002Flink",", the App Router does not reload the document. It fetches the RSC payload for the target segment, reconciles the component tree, and swaps the changed subtree in place. From the browser's perspective there is no navigation event, no document ",[349,409,410],{},"load",", and no automatic focus reset. A sighted user perceives the change because pixels move; a screen reader user perceives nothing, because the accessibility tree mutated silently while their virtual cursor stayed where it was.",[325,413,414],{},"This single behavior is the root cause of most App Router accessibility defects. The browser's native handling of full-page loads — moving focus to the top of the document, re-announcing the page title — simply does not fire. You must reconstruct that behavior in JavaScript, and you must do it at a layout level so that it applies to every route the user can reach.",[325,416,417],{},"The diagram below traces a single client-side navigation and marks the two points where accessibility logic must be injected: a focus shift to the new heading, and a polite announcement of the new page context.",[419,420,427,428,427,432,427,436,427,427,449,427,460,427,463,427,467,427,427,513,427,523,427,427,537,427,543,427,546,427,559,427,583,427,593],"svg",{"role":421,"ariaLabelledBy":422,"viewBox":425,"style":426},"img",[423,424],"navSeqTitle","navSeqDesc","0 0 760 360","width:100%;height:auto;max-width:760px","\n  ",[429,430,431],"title",{"id":423},"App Router navigation sequence with accessibility injection points",[433,434,435],"desc",{"id":424},"A horizontal sequence showing a user activating a link, the App Router fetching the RSC payload and swapping the route subtree, followed by two required accessibility steps: moving focus to the new heading and announcing the new route via a polite live region.",[437,438,441,442,427],"g",{"fill":439,"stroke":440},"var(--text)","none","\n    ",[443,444,448],"text",{"style":445,"x":446,"y":447},"font-size:18px;font-weight:700;text-anchor:middle","380","28","Client-side route transition",[450,451],"rect",{"style":452,"x":453,"y":454,"width":455,"height":456,"rx":457,"fill":458,"stroke":459},"stroke-width:2","20","56","160","120","8","var(--primary-soft)","currentColor",[450,461],{"style":452,"x":462,"y":454,"width":455,"height":456,"rx":457,"fill":458,"stroke":459},"300",[450,464],{"style":452,"x":465,"y":454,"width":455,"height":456,"rx":457,"fill":466,"stroke":459},"580","var(--surface)",[437,468,441,470,441,476,441,479,441,484,441,488,441,491,441,494,441,497,441,500,441,504,441,507,441,510,427],{"style":469,"fill":439,"stroke":440},"font-size:13px;text-anchor:middle",[443,471,475],{"style":472,"x":473,"y":474},"font-weight:700","100","86","1. User activates",[443,477,406],{"x":473,"y":478},"106",[443,480,483],{"x":473,"y":481,"fill":482},"130","var(--muted)","keyboard or",[443,485,487],{"x":473,"y":486,"fill":482},"146","pointer",[443,489,490],{"style":472,"x":446,"y":474},"2. App Router",[443,492,493],{"x":446,"y":478},"fetches RSC payload",[443,495,496],{"x":446,"y":481,"fill":482},"swaps changed",[443,498,499],{"x":446,"y":486,"fill":482},"subtree, no reload",[443,501,503],{"style":472,"x":502,"y":474},"660","3. DOM updated",[443,505,506],{"x":502,"y":478},"no focus reset",[443,508,509],{"x":502,"y":481,"fill":482},"no title",[443,511,512],{"x":502,"y":486,"fill":482},"announcement",[437,514,441,515,441,520,427],{"style":452,"stroke":459,"fill":440},[516,517],"path",{"style":518,"d":519},"marker-end:url(#navArrow)","M180 116 L300 116",[516,521],{"style":518,"d":522},"M460 116 L580 116",[524,525,441,526,427],"defs",{},[527,528,533,534,441],"marker",{"id":529,"markerWidth":530,"markerHeight":530,"refX":457,"refY":531,"orient":532},"navArrow","10","5","auto","\n      ",[516,535],{"d":536,"fill":459},"M0 0 L8 5 L0 10 z",[450,538],{"style":452,"x":539,"y":540,"width":539,"height":541,"rx":457,"fill":466,"stroke":542},"180","232","96","var(--primary)",[450,544],{"style":452,"x":545,"y":540,"width":539,"height":541,"rx":457,"fill":466,"stroke":542},"400",[437,547,441,550,441,555,427],{"style":548,"fill":549,"stroke":440},"font-size:13px;font-weight:700;text-anchor:middle","var(--primary-strong)",[443,551,554],{"x":552,"y":553},"270","262","Inject A: move focus",[443,556,558],{"x":557,"y":553},"490","Inject B: announce",[437,560,441,562,441,566,441,570,441,574,441,577,441,580,427],{"style":561,"fill":482,"stroke":440},"font-size:12px;text-anchor:middle",[443,563,565],{"x":552,"y":564},"284","focus new h1",[443,567,569],{"x":552,"y":568},"302","tabIndex=-1",[443,571,573],{"x":552,"y":572},"320","preventScroll",[443,575,576],{"x":557,"y":564},"aria-live polite",[443,578,579],{"x":557,"y":568},"new document.title",[443,581,582],{"x":557,"y":572},"route label",[437,584,441,586,441,590,427],{"style":585,"stroke":542,"fill":440},"stroke-width:2;stroke-dasharray:5 4",[516,587],{"style":588,"d":589},"marker-end:url(#navArrow2)","M660 176 L660 200 L270 200 L270 232",[516,591],{"style":588,"d":592},"M660 176 L660 200 L490 200 L490 232",[524,594,441,595,427],{},[527,596,533,598,441],{"id":597,"markerWidth":530,"markerHeight":530,"refX":457,"refY":531,"orient":532},"navArrow2",[516,599],{"d":536,"fill":542},[325,601,602,603,607],{},"The two injection points map directly to the patterns in the rest of this guide. Injection A is the focus hook covered in the next section; injection B is the route announcement covered alongside it and expanded in ",[329,604,606],{"href":605},"\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Fannouncing-client-side-route-changes-in-react\u002F","announcing client-side route changes in React",".",[394,609],{},[397,611,613],{"id":612},"routing-architecture-focus-management","Routing Architecture & Focus Management",[325,615,616],{},"Unlike the legacy Pages Router, the App Router leverages a server-driven navigation model. While this improves initial load performance, it removes automatic client-side focus resets. Developers must explicitly manage focus to prevent keyboard users from losing their place after navigation.",[325,618,619,620,623,624,627,628,631,632,636],{},"Implement ",[349,621,622],{},"usePathname"," and ",[349,625,626],{},"useEffect"," to programmatically shift focus to route headings. Always pair this with ",[349,629,630],{},"document.title"," updates to provide immediate screen reader context. For bypassing repetitive navigation blocks, refer to established patterns for ",[329,633,635],{"href":634},"\u002Freact-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Fimplementing-skip-links-in-nextjs-app-router\u002F","Implementing skip links in Next.js App Router",". When handling parallel routes, manage independent focus contexts per segment to avoid competing announcements.",[638,639,644],"pre",{"className":640,"code":641,"language":642,"meta":643,"style":643},"language-tsx shiki shiki-themes github-light github-dark","\u002F\u002F hooks\u002FuseRouteFocus.ts\n\"use client\";\nimport { useEffect, useRef } from \"react\";\nimport { usePathname } from \"next\u002Fnavigation\";\n\n\u002F**\n * Automatically restores focus to the primary heading after route changes.\n * Must be used inside a Client Component or Layout.\n *\u002F\nexport function useRouteFocus() {\n  const pathname = usePathname();\n  const headingRef = useRef\u003CHTMLHeadingElement>(null);\n\n  useEffect(() => {\n    if (headingRef.current) {\n      \u002F\u002F preventScroll avoids jarring viewport jumps on mobile\n      headingRef.current.focus({ preventScroll: true });\n    }\n  }, [pathname]);\n\n  return headingRef;\n}\n\n\u002F\u002F Usage in a Client Component:\n\u002F\u002F const headingRef = useRouteFocus();\n\u002F\u002F return \u003Ch1 ref={headingRef} tabIndex={-1}>Page Title\u003C\u002Fh1>;\n","tsx","",[349,645,646,655,666,684,699,706,712,718,724,730,746,765,793,798,813,822,828,846,852,858,863,872,878,883,889,895],{"__ignoreMap":643},[647,648,651],"span",{"class":649,"line":650},"line",1,[647,652,654],{"class":653},"sJ8bj","\u002F\u002F hooks\u002FuseRouteFocus.ts\n",[647,656,658,662],{"class":649,"line":657},2,[647,659,661],{"class":660},"sZZnC","\"use client\"",[647,663,665],{"class":664},"sVt8B",";\n",[647,667,669,673,676,679,682],{"class":649,"line":668},3,[647,670,672],{"class":671},"szBVR","import",[647,674,675],{"class":664}," { useEffect, useRef } ",[647,677,678],{"class":671},"from",[647,680,681],{"class":660}," \"react\"",[647,683,665],{"class":664},[647,685,687,689,692,694,697],{"class":649,"line":686},4,[647,688,672],{"class":671},[647,690,691],{"class":664}," { usePathname } ",[647,693,678],{"class":671},[647,695,696],{"class":660}," \"next\u002Fnavigation\"",[647,698,665],{"class":664},[647,700,702],{"class":649,"line":701},5,[647,703,705],{"emptyLinePlaceholder":704},true,"\n",[647,707,709],{"class":649,"line":708},6,[647,710,711],{"class":653},"\u002F**\n",[647,713,715],{"class":649,"line":714},7,[647,716,717],{"class":653}," * Automatically restores focus to the primary heading after route changes.\n",[647,719,721],{"class":649,"line":720},8,[647,722,723],{"class":653}," * Must be used inside a Client Component or Layout.\n",[647,725,727],{"class":649,"line":726},9,[647,728,729],{"class":653}," *\u002F\n",[647,731,733,736,739,743],{"class":649,"line":732},10,[647,734,735],{"class":671},"export",[647,737,738],{"class":671}," function",[647,740,742],{"class":741},"sScJk"," useRouteFocus",[647,744,745],{"class":664},"() {\n",[647,747,749,752,756,759,762],{"class":649,"line":748},11,[647,750,751],{"class":671},"  const",[647,753,755],{"class":754},"sj4cs"," pathname",[647,757,758],{"class":671}," =",[647,760,761],{"class":741}," usePathname",[647,763,764],{"class":664},"();\n",[647,766,768,770,773,775,778,781,784,787,790],{"class":649,"line":767},12,[647,769,751],{"class":671},[647,771,772],{"class":754}," headingRef",[647,774,758],{"class":671},[647,776,777],{"class":741}," useRef",[647,779,780],{"class":664},"\u003C",[647,782,783],{"class":741},"HTMLHeadingElement",[647,785,786],{"class":664},">(",[647,788,789],{"class":754},"null",[647,791,792],{"class":664},");\n",[647,794,796],{"class":649,"line":795},13,[647,797,705],{"emptyLinePlaceholder":704},[647,799,801,804,807,810],{"class":649,"line":800},14,[647,802,803],{"class":741},"  useEffect",[647,805,806],{"class":664},"(() ",[647,808,809],{"class":671},"=>",[647,811,812],{"class":664}," {\n",[647,814,816,819],{"class":649,"line":815},15,[647,817,818],{"class":671},"    if",[647,820,821],{"class":664}," (headingRef.current) {\n",[647,823,825],{"class":649,"line":824},16,[647,826,827],{"class":653},"      \u002F\u002F preventScroll avoids jarring viewport jumps on mobile\n",[647,829,831,834,837,840,843],{"class":649,"line":830},17,[647,832,833],{"class":664},"      headingRef.current.",[647,835,836],{"class":741},"focus",[647,838,839],{"class":664},"({ preventScroll: ",[647,841,842],{"class":754},"true",[647,844,845],{"class":664}," });\n",[647,847,849],{"class":649,"line":848},18,[647,850,851],{"class":664},"    }\n",[647,853,855],{"class":649,"line":854},19,[647,856,857],{"class":664},"  }, [pathname]);\n",[647,859,861],{"class":649,"line":860},20,[647,862,705],{"emptyLinePlaceholder":704},[647,864,866,869],{"class":649,"line":865},21,[647,867,868],{"class":671},"  return",[647,870,871],{"class":664}," headingRef;\n",[647,873,875],{"class":649,"line":874},22,[647,876,877],{"class":664},"}\n",[647,879,881],{"class":649,"line":880},23,[647,882,705],{"emptyLinePlaceholder":704},[647,884,886],{"class":649,"line":885},24,[647,887,888],{"class":653},"\u002F\u002F Usage in a Client Component:\n",[647,890,892],{"class":649,"line":891},25,[647,893,894],{"class":653},"\u002F\u002F const headingRef = useRouteFocus();\n",[647,896,898],{"class":649,"line":897},26,[647,899,900],{"class":653},"\u002F\u002F return \u003Ch1 ref={headingRef} tabIndex={-1}>Page Title\u003C\u002Fh1>;\n",[325,902,903,904,906,907,909,910,913],{},"There is a subtle ordering problem worth understanding. The ",[349,905,622],{}," value changes the instant the URL updates, but the new heading may not have committed to the DOM on the very same render when the previous page is still streaming out. Because ",[349,908,626],{}," runs after commit, the ref will point at the heading that is actually mounted at that moment — which is exactly what you want. Avoid ",[349,911,912],{},"useLayoutEffect"," here: it fires before paint and can grab focus while the old subtree is still present, producing a flicker where focus lands and then jumps again.",[325,915,916],{},"For route announcements (injection B above), keep a single persistent live region high in the tree and write the new route label into it after the focus shift. A common refinement is to debounce the announcement by one frame so that the focus move (which itself causes a screen reader to read the focused heading) and the live-region text do not collide in the speech queue:",[638,918,920],{"className":640,"code":919,"language":642,"meta":643,"style":643},"\u002F\u002F components\u002FRouteAnnouncer.tsx\n\"use client\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { usePathname } from \"next\u002Fnavigation\";\n\nexport function RouteAnnouncer({ label }: { label: string }) {\n  const pathname = usePathname();\n  const [message, setMessage] = useState(\"\");\n  const isFirst = useRef(true);\n\n  useEffect(() => {\n    \u002F\u002F Skip the initial mount: the browser already announced the page.\n    if (isFirst.current) {\n      isFirst.current = false;\n      return;\n    }\n    const id = requestAnimationFrame(() => setMessage(`${label} page`));\n    return () => cancelAnimationFrame(id);\n  }, [pathname, label]);\n\n  return (\n    \u003Cdiv aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\" role=\"status\">\n      {message}\n    \u003C\u002Fdiv>\n  );\n}\n",[349,921,922,927,933,946,958,962,997,1009,1042,1059,1063,1073,1078,1085,1097,1104,1108,1141,1157,1162,1166,1173,1217,1222,1231,1236],{"__ignoreMap":643},[647,923,924],{"class":649,"line":650},[647,925,926],{"class":653},"\u002F\u002F components\u002FRouteAnnouncer.tsx\n",[647,928,929,931],{"class":649,"line":657},[647,930,661],{"class":660},[647,932,665],{"class":664},[647,934,935,937,940,942,944],{"class":649,"line":668},[647,936,672],{"class":671},[647,938,939],{"class":664}," { useEffect, useRef, useState } ",[647,941,678],{"class":671},[647,943,681],{"class":660},[647,945,665],{"class":664},[647,947,948,950,952,954,956],{"class":649,"line":686},[647,949,672],{"class":671},[647,951,691],{"class":664},[647,953,678],{"class":671},[647,955,696],{"class":660},[647,957,665],{"class":664},[647,959,960],{"class":649,"line":701},[647,961,705],{"emptyLinePlaceholder":704},[647,963,964,966,968,971,974,978,981,984,987,989,991,994],{"class":649,"line":708},[647,965,735],{"class":671},[647,967,738],{"class":671},[647,969,970],{"class":741}," RouteAnnouncer",[647,972,973],{"class":664},"({ ",[647,975,977],{"class":976},"s4XuR","label",[647,979,980],{"class":664}," }",[647,982,983],{"class":671},":",[647,985,986],{"class":664}," { ",[647,988,977],{"class":976},[647,990,983],{"class":671},[647,992,993],{"class":754}," string",[647,995,996],{"class":664}," }) {\n",[647,998,999,1001,1003,1005,1007],{"class":649,"line":714},[647,1000,751],{"class":671},[647,1002,755],{"class":754},[647,1004,758],{"class":671},[647,1006,761],{"class":741},[647,1008,764],{"class":664},[647,1010,1011,1013,1016,1019,1022,1025,1028,1031,1034,1037,1040],{"class":649,"line":720},[647,1012,751],{"class":671},[647,1014,1015],{"class":664}," [",[647,1017,1018],{"class":754},"message",[647,1020,1021],{"class":664},", ",[647,1023,1024],{"class":754},"setMessage",[647,1026,1027],{"class":664},"] ",[647,1029,1030],{"class":671},"=",[647,1032,1033],{"class":741}," useState",[647,1035,1036],{"class":664},"(",[647,1038,1039],{"class":660},"\"\"",[647,1041,792],{"class":664},[647,1043,1044,1046,1049,1051,1053,1055,1057],{"class":649,"line":726},[647,1045,751],{"class":671},[647,1047,1048],{"class":754}," isFirst",[647,1050,758],{"class":671},[647,1052,777],{"class":741},[647,1054,1036],{"class":664},[647,1056,842],{"class":754},[647,1058,792],{"class":664},[647,1060,1061],{"class":649,"line":732},[647,1062,705],{"emptyLinePlaceholder":704},[647,1064,1065,1067,1069,1071],{"class":649,"line":748},[647,1066,803],{"class":741},[647,1068,806],{"class":664},[647,1070,809],{"class":671},[647,1072,812],{"class":664},[647,1074,1075],{"class":649,"line":767},[647,1076,1077],{"class":653},"    \u002F\u002F Skip the initial mount: the browser already announced the page.\n",[647,1079,1080,1082],{"class":649,"line":795},[647,1081,818],{"class":671},[647,1083,1084],{"class":664}," (isFirst.current) {\n",[647,1086,1087,1090,1092,1095],{"class":649,"line":800},[647,1088,1089],{"class":664},"      isFirst.current ",[647,1091,1030],{"class":671},[647,1093,1094],{"class":754}," false",[647,1096,665],{"class":664},[647,1098,1099,1102],{"class":649,"line":815},[647,1100,1101],{"class":671},"      return",[647,1103,665],{"class":664},[647,1105,1106],{"class":649,"line":824},[647,1107,851],{"class":664},[647,1109,1110,1113,1116,1118,1121,1123,1125,1128,1130,1133,1135,1138],{"class":649,"line":830},[647,1111,1112],{"class":671},"    const",[647,1114,1115],{"class":754}," id",[647,1117,758],{"class":671},[647,1119,1120],{"class":741}," requestAnimationFrame",[647,1122,806],{"class":664},[647,1124,809],{"class":671},[647,1126,1127],{"class":741}," setMessage",[647,1129,1036],{"class":664},[647,1131,1132],{"class":660},"`${",[647,1134,977],{"class":664},[647,1136,1137],{"class":660},"} page`",[647,1139,1140],{"class":664},"));\n",[647,1142,1143,1146,1149,1151,1154],{"class":649,"line":848},[647,1144,1145],{"class":671},"    return",[647,1147,1148],{"class":664}," () ",[647,1150,809],{"class":671},[647,1152,1153],{"class":741}," cancelAnimationFrame",[647,1155,1156],{"class":664},"(id);\n",[647,1158,1159],{"class":649,"line":854},[647,1160,1161],{"class":664},"  }, [pathname, label]);\n",[647,1163,1164],{"class":649,"line":860},[647,1165,705],{"emptyLinePlaceholder":704},[647,1167,1168,1170],{"class":649,"line":865},[647,1169,868],{"class":671},[647,1171,1172],{"class":664}," (\n",[647,1174,1175,1178,1182,1185,1187,1190,1193,1195,1198,1201,1203,1206,1209,1211,1214],{"class":649,"line":874},[647,1176,1177],{"class":664},"    \u003C",[647,1179,1181],{"class":1180},"s9eBZ","div",[647,1183,1184],{"class":741}," aria-live",[647,1186,1030],{"class":671},[647,1188,1189],{"class":660},"\"polite\"",[647,1191,1192],{"class":741}," aria-atomic",[647,1194,1030],{"class":671},[647,1196,1197],{"class":660},"\"true\"",[647,1199,1200],{"class":741}," className",[647,1202,1030],{"class":671},[647,1204,1205],{"class":660},"\"sr-only\"",[647,1207,1208],{"class":741}," role",[647,1210,1030],{"class":671},[647,1212,1213],{"class":660},"\"status\"",[647,1215,1216],{"class":664},">\n",[647,1218,1219],{"class":649,"line":880},[647,1220,1221],{"class":664},"      {message}\n",[647,1223,1224,1227,1229],{"class":649,"line":885},[647,1225,1226],{"class":664},"    \u003C\u002F",[647,1228,1181],{"class":1180},[647,1230,1216],{"class":664},[647,1232,1233],{"class":649,"line":891},[647,1234,1235],{"class":664},"  );\n",[647,1237,1238],{"class":649,"line":897},[647,1239,877],{"class":664},[325,1241,1242,1245,1246,86,1249,1252],{},[339,1243,1244],{},"Testing Hook:"," Verify focus moves to the main content heading on every route change using keyboard-only navigation (",[349,1247,1248],{},"Tab",[349,1250,1251],{},"Shift+Tab","). Validate screen reader output announces the new route context immediately after the transition.",[394,1254],{},[397,1256,1258],{"id":1257},"server-components-vs-client-interactivity-boundaries","Server Components vs Client Interactivity Boundaries",[325,1260,1261,1262,1264],{},"Hybrid rendering in the App Router demands strict architectural boundaries. Keep static ARIA attributes (landmarks, labels, structural roles) in Server Components for optimal performance and SEO. Isolate dynamic state—modals, tabs, accordions, and live regions—to Client Components using the ",[349,1263,661],{}," directive.",[325,1266,1267,1268,1271,1272,1275,1276,607],{},"Leverage established patterns from ",[329,1269,181],{"href":1270},"\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002F"," to manage focus traps and ",[349,1273,1274],{},"aria-live"," updates strictly within client boundaries. Avoid passing complex interactive components as props across server\u002Fclient boundaries, as this triggers hydration mismatches and breaks ARIA state synchronization. For a deeper treatment of where to draw the line, see the related guide on ",[329,1277,1279],{"href":1278},"\u002Freact-nextjs-accessibility-patterns\u002Fserver-components-client-side-interactivity\u002F","server components and client-side interactivity",[325,1281,1282,1283,1287,1288,1291,1292,1295,1296,1299,1300,1302],{},"A useful rule of thumb: anything that ",[1284,1285,1286],"em",{},"describes"," content can live on the server; anything that ",[1284,1289,1290],{},"reacts"," to the user must live on the client. Landmark roles, headings, labels, and ",[349,1293,1294],{},"lang"," attributes are descriptions and serialize cleanly. Focus traps, ",[349,1297,1298],{},"aria-expanded"," toggles, ",[349,1301,1274],{}," writes, and keyboard handlers are reactions and require hydration. Drawing the boundary this way keeps your client bundle small without sacrificing the semantic scaffolding that assistive technology depends on.",[638,1304,1306],{"className":640,"code":1305,"language":642,"meta":643,"style":643},"\u002F\u002F components\u002FServerLayout.tsx (RSC by default)\nimport { ClientModalController } from \".\u002FClientModalController\";\n\nexport default function ServerLayout({ children }: { children: React.ReactNode }) {\n  return (\n    \u003Cdiv className=\"app-container\">\n      \u003Cnav aria-label=\"Global navigation\">\n        {\u002F* Static links serialized at build\u002Frequest time *\u002F}\n      \u003C\u002Fnav>\n      \u003Cmain id=\"main-content\" role=\"main\" tabIndex={-1}>\n        {children}\n        {\u002F* Client boundary isolates interactive state *\u002F}\n        \u003CClientModalController \u002F>\n      \u003C\u002Fmain>\n    \u003C\u002Fdiv>\n  );\n}\n",[349,1307,1308,1313,1327,1331,1368,1374,1389,1407,1417,1426,1464,1469,1478,1489,1497,1505,1509],{"__ignoreMap":643},[647,1309,1310],{"class":649,"line":650},[647,1311,1312],{"class":653},"\u002F\u002F components\u002FServerLayout.tsx (RSC by default)\n",[647,1314,1315,1317,1320,1322,1325],{"class":649,"line":657},[647,1316,672],{"class":671},[647,1318,1319],{"class":664}," { ClientModalController } ",[647,1321,678],{"class":671},[647,1323,1324],{"class":660}," \".\u002FClientModalController\"",[647,1326,665],{"class":664},[647,1328,1329],{"class":649,"line":668},[647,1330,705],{"emptyLinePlaceholder":704},[647,1332,1333,1335,1338,1340,1343,1345,1348,1350,1352,1354,1356,1358,1361,1363,1366],{"class":649,"line":686},[647,1334,735],{"class":671},[647,1336,1337],{"class":671}," default",[647,1339,738],{"class":671},[647,1341,1342],{"class":741}," ServerLayout",[647,1344,973],{"class":664},[647,1346,1347],{"class":976},"children",[647,1349,980],{"class":664},[647,1351,983],{"class":671},[647,1353,986],{"class":664},[647,1355,1347],{"class":976},[647,1357,983],{"class":671},[647,1359,1360],{"class":741}," React",[647,1362,607],{"class":664},[647,1364,1365],{"class":741},"ReactNode",[647,1367,996],{"class":664},[647,1369,1370,1372],{"class":649,"line":701},[647,1371,868],{"class":671},[647,1373,1172],{"class":664},[647,1375,1376,1378,1380,1382,1384,1387],{"class":649,"line":708},[647,1377,1177],{"class":664},[647,1379,1181],{"class":1180},[647,1381,1200],{"class":741},[647,1383,1030],{"class":671},[647,1385,1386],{"class":660},"\"app-container\"",[647,1388,1216],{"class":664},[647,1390,1391,1394,1397,1400,1402,1405],{"class":649,"line":714},[647,1392,1393],{"class":664},"      \u003C",[647,1395,1396],{"class":1180},"nav",[647,1398,1399],{"class":741}," aria-label",[647,1401,1030],{"class":671},[647,1403,1404],{"class":660},"\"Global navigation\"",[647,1406,1216],{"class":664},[647,1408,1409,1412,1415],{"class":649,"line":720},[647,1410,1411],{"class":664},"        {",[647,1413,1414],{"class":653},"\u002F* Static links serialized at build\u002Frequest time *\u002F",[647,1416,877],{"class":664},[647,1418,1419,1422,1424],{"class":649,"line":726},[647,1420,1421],{"class":664},"      \u003C\u002F",[647,1423,1396],{"class":1180},[647,1425,1216],{"class":664},[647,1427,1428,1430,1433,1435,1437,1440,1442,1444,1447,1450,1452,1455,1458,1461],{"class":649,"line":732},[647,1429,1393],{"class":664},[647,1431,1432],{"class":1180},"main",[647,1434,1115],{"class":741},[647,1436,1030],{"class":671},[647,1438,1439],{"class":660},"\"main-content\"",[647,1441,1208],{"class":741},[647,1443,1030],{"class":671},[647,1445,1446],{"class":660},"\"main\"",[647,1448,1449],{"class":741}," tabIndex",[647,1451,1030],{"class":671},[647,1453,1454],{"class":664},"{",[647,1456,1457],{"class":671},"-",[647,1459,1460],{"class":754},"1",[647,1462,1463],{"class":664},"}>\n",[647,1465,1466],{"class":649,"line":748},[647,1467,1468],{"class":664},"        {children}\n",[647,1470,1471,1473,1476],{"class":649,"line":767},[647,1472,1411],{"class":664},[647,1474,1475],{"class":653},"\u002F* Client boundary isolates interactive state *\u002F",[647,1477,877],{"class":664},[647,1479,1480,1483,1486],{"class":649,"line":795},[647,1481,1482],{"class":664},"        \u003C",[647,1484,1485],{"class":754},"ClientModalController",[647,1487,1488],{"class":664}," \u002F>\n",[647,1490,1491,1493,1495],{"class":649,"line":800},[647,1492,1421],{"class":664},[647,1494,1432],{"class":1180},[647,1496,1216],{"class":664},[647,1498,1499,1501,1503],{"class":649,"line":815},[647,1500,1226],{"class":664},[647,1502,1181],{"class":1180},[647,1504,1216],{"class":664},[647,1506,1507],{"class":649,"line":824},[647,1508,1235],{"class":664},[647,1510,1511],{"class":649,"line":830},[647,1512,877],{"class":664},[638,1514,1516],{"className":640,"code":1515,"language":642,"meta":643,"style":643},"\u002F\u002F components\u002FClientModalController.tsx\n\"use client\";\nimport { useEffect, useRef, useCallback, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\ninterface ModalProps {\n  isOpen: boolean;\n  onClose: () => void;\n  title: string;\n  children: React.ReactNode;\n}\n\nexport function ClientModalController({ isOpen, onClose, title, children }: ModalProps) {\n  const dialogRef = useRef\u003CHTMLDialogElement>(null);\n  const previousFocus = useRef\u003CElement | null>(null);\n\n  const handleKeyDown = useCallback((e: KeyboardEvent) => {\n    if (e.key === \"Escape\") onClose();\n    \u002F\u002F Add robust tab-trapping logic here for production\n  }, [onClose]);\n\n  useEffect(() => {\n    if (isOpen) {\n      previousFocus.current = document.activeElement;\n      document.addEventListener(\"keydown\", handleKeyDown);\n      dialogRef.current?.showModal();\n      dialogRef.current?.focus();\n    } else {\n      (previousFocus.current as HTMLElement | null)?.focus();\n      document.removeEventListener(\"keydown\", handleKeyDown);\n    }\n    return () => document.removeEventListener(\"keydown\", handleKeyDown);\n  }, [isOpen, handleKeyDown]);\n\n  if (!isOpen) return null;\n\n  return createPortal(\n    \u003Cdialog\n      ref={dialogRef}\n      aria-modal=\"true\"\n      aria-labelledby=\"dialog-title\"\n      className=\"modal-overlay\"\n    >\n      \u003Ch2 id=\"dialog-title\">{title}\u003C\u002Fh2>\n      {children}\n      \u003Cbutton onClick={onClose}>Close\u003C\u002Fbutton>\n    \u003C\u002Fdialog>,\n    document.body\n  );\n}\n",[349,1517,1518,1523,1529,1542,1556,1560,1570,1582,1598,1609,1624,1628,1632,1668,1690,1718,1722,1752,1771,1776,1781,1785,1795,1802,1812,1828,1838,1847,1858,1881,1895,1900,1920,1926,1931,1953,1958,1969,1977,1988,1999,2010,2021,2027,2048,2054,2074,2085,2091,2096],{"__ignoreMap":643},[647,1519,1520],{"class":649,"line":650},[647,1521,1522],{"class":653},"\u002F\u002F components\u002FClientModalController.tsx\n",[647,1524,1525,1527],{"class":649,"line":657},[647,1526,661],{"class":660},[647,1528,665],{"class":664},[647,1530,1531,1533,1536,1538,1540],{"class":649,"line":668},[647,1532,672],{"class":671},[647,1534,1535],{"class":664}," { useEffect, useRef, useCallback, useState } ",[647,1537,678],{"class":671},[647,1539,681],{"class":660},[647,1541,665],{"class":664},[647,1543,1544,1546,1549,1551,1554],{"class":649,"line":686},[647,1545,672],{"class":671},[647,1547,1548],{"class":664}," { createPortal } ",[647,1550,678],{"class":671},[647,1552,1553],{"class":660}," \"react-dom\"",[647,1555,665],{"class":664},[647,1557,1558],{"class":649,"line":701},[647,1559,705],{"emptyLinePlaceholder":704},[647,1561,1562,1565,1568],{"class":649,"line":708},[647,1563,1564],{"class":671},"interface",[647,1566,1567],{"class":741}," ModalProps",[647,1569,812],{"class":664},[647,1571,1572,1575,1577,1580],{"class":649,"line":714},[647,1573,1574],{"class":976},"  isOpen",[647,1576,983],{"class":671},[647,1578,1579],{"class":754}," boolean",[647,1581,665],{"class":664},[647,1583,1584,1587,1589,1591,1593,1596],{"class":649,"line":720},[647,1585,1586],{"class":741},"  onClose",[647,1588,983],{"class":671},[647,1590,1148],{"class":664},[647,1592,809],{"class":671},[647,1594,1595],{"class":754}," void",[647,1597,665],{"class":664},[647,1599,1600,1603,1605,1607],{"class":649,"line":726},[647,1601,1602],{"class":976},"  title",[647,1604,983],{"class":671},[647,1606,993],{"class":754},[647,1608,665],{"class":664},[647,1610,1611,1614,1616,1618,1620,1622],{"class":649,"line":732},[647,1612,1613],{"class":976},"  children",[647,1615,983],{"class":671},[647,1617,1360],{"class":741},[647,1619,607],{"class":664},[647,1621,1365],{"class":741},[647,1623,665],{"class":664},[647,1625,1626],{"class":649,"line":748},[647,1627,877],{"class":664},[647,1629,1630],{"class":649,"line":767},[647,1631,705],{"emptyLinePlaceholder":704},[647,1633,1634,1636,1638,1641,1643,1646,1648,1651,1653,1655,1657,1659,1661,1663,1665],{"class":649,"line":795},[647,1635,735],{"class":671},[647,1637,738],{"class":671},[647,1639,1640],{"class":741}," ClientModalController",[647,1642,973],{"class":664},[647,1644,1645],{"class":976},"isOpen",[647,1647,1021],{"class":664},[647,1649,1650],{"class":976},"onClose",[647,1652,1021],{"class":664},[647,1654,429],{"class":976},[647,1656,1021],{"class":664},[647,1658,1347],{"class":976},[647,1660,980],{"class":664},[647,1662,983],{"class":671},[647,1664,1567],{"class":741},[647,1666,1667],{"class":664},") {\n",[647,1669,1670,1672,1675,1677,1679,1681,1684,1686,1688],{"class":649,"line":800},[647,1671,751],{"class":671},[647,1673,1674],{"class":754}," dialogRef",[647,1676,758],{"class":671},[647,1678,777],{"class":741},[647,1680,780],{"class":664},[647,1682,1683],{"class":741},"HTMLDialogElement",[647,1685,786],{"class":664},[647,1687,789],{"class":754},[647,1689,792],{"class":664},[647,1691,1692,1694,1697,1699,1701,1703,1706,1709,1712,1714,1716],{"class":649,"line":815},[647,1693,751],{"class":671},[647,1695,1696],{"class":754}," previousFocus",[647,1698,758],{"class":671},[647,1700,777],{"class":741},[647,1702,780],{"class":664},[647,1704,1705],{"class":741},"Element",[647,1707,1708],{"class":671}," |",[647,1710,1711],{"class":754}," null",[647,1713,786],{"class":664},[647,1715,789],{"class":754},[647,1717,792],{"class":664},[647,1719,1720],{"class":649,"line":824},[647,1721,705],{"emptyLinePlaceholder":704},[647,1723,1724,1726,1729,1731,1734,1737,1740,1742,1745,1748,1750],{"class":649,"line":830},[647,1725,751],{"class":671},[647,1727,1728],{"class":754}," handleKeyDown",[647,1730,758],{"class":671},[647,1732,1733],{"class":741}," useCallback",[647,1735,1736],{"class":664},"((",[647,1738,1739],{"class":976},"e",[647,1741,983],{"class":671},[647,1743,1744],{"class":741}," KeyboardEvent",[647,1746,1747],{"class":664},") ",[647,1749,809],{"class":671},[647,1751,812],{"class":664},[647,1753,1754,1756,1759,1762,1765,1767,1769],{"class":649,"line":848},[647,1755,818],{"class":671},[647,1757,1758],{"class":664}," (e.key ",[647,1760,1761],{"class":671},"===",[647,1763,1764],{"class":660}," \"Escape\"",[647,1766,1747],{"class":664},[647,1768,1650],{"class":741},[647,1770,764],{"class":664},[647,1772,1773],{"class":649,"line":854},[647,1774,1775],{"class":653},"    \u002F\u002F Add robust tab-trapping logic here for production\n",[647,1777,1778],{"class":649,"line":860},[647,1779,1780],{"class":664},"  }, [onClose]);\n",[647,1782,1783],{"class":649,"line":865},[647,1784,705],{"emptyLinePlaceholder":704},[647,1786,1787,1789,1791,1793],{"class":649,"line":874},[647,1788,803],{"class":741},[647,1790,806],{"class":664},[647,1792,809],{"class":671},[647,1794,812],{"class":664},[647,1796,1797,1799],{"class":649,"line":880},[647,1798,818],{"class":671},[647,1800,1801],{"class":664}," (isOpen) {\n",[647,1803,1804,1807,1809],{"class":649,"line":885},[647,1805,1806],{"class":664},"      previousFocus.current ",[647,1808,1030],{"class":671},[647,1810,1811],{"class":664}," document.activeElement;\n",[647,1813,1814,1817,1820,1822,1825],{"class":649,"line":891},[647,1815,1816],{"class":664},"      document.",[647,1818,1819],{"class":741},"addEventListener",[647,1821,1036],{"class":664},[647,1823,1824],{"class":660},"\"keydown\"",[647,1826,1827],{"class":664},", handleKeyDown);\n",[647,1829,1830,1833,1836],{"class":649,"line":897},[647,1831,1832],{"class":664},"      dialogRef.current?.",[647,1834,1835],{"class":741},"showModal",[647,1837,764],{"class":664},[647,1839,1841,1843,1845],{"class":649,"line":1840},27,[647,1842,1832],{"class":664},[647,1844,836],{"class":741},[647,1846,764],{"class":664},[647,1848,1850,1853,1856],{"class":649,"line":1849},28,[647,1851,1852],{"class":664},"    } ",[647,1854,1855],{"class":671},"else",[647,1857,812],{"class":664},[647,1859,1861,1864,1867,1870,1872,1874,1877,1879],{"class":649,"line":1860},29,[647,1862,1863],{"class":664},"      (previousFocus.current ",[647,1865,1866],{"class":671},"as",[647,1868,1869],{"class":741}," HTMLElement",[647,1871,1708],{"class":671},[647,1873,1711],{"class":754},[647,1875,1876],{"class":664},")?.",[647,1878,836],{"class":741},[647,1880,764],{"class":664},[647,1882,1884,1886,1889,1891,1893],{"class":649,"line":1883},30,[647,1885,1816],{"class":664},[647,1887,1888],{"class":741},"removeEventListener",[647,1890,1036],{"class":664},[647,1892,1824],{"class":660},[647,1894,1827],{"class":664},[647,1896,1898],{"class":649,"line":1897},31,[647,1899,851],{"class":664},[647,1901,1903,1905,1907,1909,1912,1914,1916,1918],{"class":649,"line":1902},32,[647,1904,1145],{"class":671},[647,1906,1148],{"class":664},[647,1908,809],{"class":671},[647,1910,1911],{"class":664}," document.",[647,1913,1888],{"class":741},[647,1915,1036],{"class":664},[647,1917,1824],{"class":660},[647,1919,1827],{"class":664},[647,1921,1923],{"class":649,"line":1922},33,[647,1924,1925],{"class":664},"  }, [isOpen, handleKeyDown]);\n",[647,1927,1929],{"class":649,"line":1928},34,[647,1930,705],{"emptyLinePlaceholder":704},[647,1932,1934,1937,1940,1943,1946,1949,1951],{"class":649,"line":1933},35,[647,1935,1936],{"class":671},"  if",[647,1938,1939],{"class":664}," (",[647,1941,1942],{"class":671},"!",[647,1944,1945],{"class":664},"isOpen) ",[647,1947,1948],{"class":671},"return",[647,1950,1711],{"class":754},[647,1952,665],{"class":664},[647,1954,1956],{"class":649,"line":1955},36,[647,1957,705],{"emptyLinePlaceholder":704},[647,1959,1961,1963,1966],{"class":649,"line":1960},37,[647,1962,868],{"class":671},[647,1964,1965],{"class":741}," createPortal",[647,1967,1968],{"class":664},"(\n",[647,1970,1972,1974],{"class":649,"line":1971},38,[647,1973,1177],{"class":664},[647,1975,1976],{"class":1180},"dialog\n",[647,1978,1980,1983,1985],{"class":649,"line":1979},39,[647,1981,1982],{"class":741},"      ref",[647,1984,1030],{"class":671},[647,1986,1987],{"class":664},"{dialogRef}\n",[647,1989,1991,1994,1996],{"class":649,"line":1990},40,[647,1992,1993],{"class":741},"      aria-modal",[647,1995,1030],{"class":671},[647,1997,1998],{"class":660},"\"true\"\n",[647,2000,2002,2005,2007],{"class":649,"line":2001},41,[647,2003,2004],{"class":741},"      aria-labelledby",[647,2006,1030],{"class":671},[647,2008,2009],{"class":660},"\"dialog-title\"\n",[647,2011,2013,2016,2018],{"class":649,"line":2012},42,[647,2014,2015],{"class":741},"      className",[647,2017,1030],{"class":671},[647,2019,2020],{"class":660},"\"modal-overlay\"\n",[647,2022,2024],{"class":649,"line":2023},43,[647,2025,2026],{"class":664},"    >\n",[647,2028,2030,2032,2034,2036,2038,2041,2044,2046],{"class":649,"line":2029},44,[647,2031,1393],{"class":664},[647,2033,397],{"class":1180},[647,2035,1115],{"class":741},[647,2037,1030],{"class":671},[647,2039,2040],{"class":660},"\"dialog-title\"",[647,2042,2043],{"class":664},">{title}\u003C\u002F",[647,2045,397],{"class":1180},[647,2047,1216],{"class":664},[647,2049,2051],{"class":649,"line":2050},45,[647,2052,2053],{"class":664},"      {children}\n",[647,2055,2057,2059,2062,2065,2067,2070,2072],{"class":649,"line":2056},46,[647,2058,1393],{"class":664},[647,2060,2061],{"class":1180},"button",[647,2063,2064],{"class":741}," onClick",[647,2066,1030],{"class":671},[647,2068,2069],{"class":664},"{onClose}>Close\u003C\u002F",[647,2071,2061],{"class":1180},[647,2073,1216],{"class":664},[647,2075,2077,2079,2082],{"class":649,"line":2076},47,[647,2078,1226],{"class":664},[647,2080,2081],{"class":1180},"dialog",[647,2083,2084],{"class":664},">,\n",[647,2086,2088],{"class":649,"line":2087},48,[647,2089,2090],{"class":664},"    document.body\n",[647,2092,2094],{"class":649,"line":2093},49,[647,2095,1235],{"class":664},[647,2097,2099],{"class":649,"line":2098},50,[647,2100,877],{"class":664},[325,2102,2103,2104,1021,2106,2109,2110,1021,2113,2116,2117,2119],{},"A practical hydration gotcha specific to the App Router: when a Server Component renders an interactive child, the child's accessibility state (",[349,2105,1298],{},[349,2107,2108],{},"aria-selected",") must have a deterministic initial value that matches on both server and client. If you derive that state from ",[349,2111,2112],{},"window.matchMedia",[349,2114,2115],{},"localStorage",", or any browser-only API during render, the server will emit one value and the client another, and React will discard the server markup along with any ARIA attributes you set on it. Read browser state inside ",[349,2118,626],{}," and update after mount instead.",[325,2121,2122,2124,2125,1021,2127,1021,2130,2133],{},[339,2123,1244],{}," Monitor the browser console for React hydration warnings during development. Validate that ARIA states (",[349,2126,1298],{},[349,2128,2129],{},"aria-hidden",[349,2131,2132],{},"aria-modal",") update correctly without triggering full page reloads or layout shifts.",[394,2135],{},[397,2137,2139],{"id":2138},"component-architecture-library-integration","Component Architecture & Library Integration",[325,2141,2142,2143,2146],{},"When selecting third-party UI libraries for the App Router, evaluate them for explicit server\u002Fclient component support and RSC compatibility. Extend base components with framework-specific routing props while strictly preserving native ARIA semantics. Consult vetted foundations from ",[329,2144,97],{"href":2145},"\u002Freact-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react\u002F"," to reduce implementation overhead.",[325,2148,2149,2150,2152,2153,2156,2157,623,2160,2163,2164,2167,2168,2171,2172,2175],{},"Many headless libraries assume a fully client-rendered environment and call ",[349,2151,661],{}," implicitly through their hooks. Wrapping such a component in a thin client boundary is fine, but watch for two failure modes. First, libraries that generate ",[349,2154,2155],{},"id"," values with a non-deterministic counter will produce different IDs on server and client, breaking ",[349,2158,2159],{},"aria-labelledby",[349,2161,2162],{},"aria-controls"," associations on hydration; prefer libraries built on React's ",[349,2165,2166],{},"useId",", which is hydration-stable by design. Second, components that portal into ",[349,2169,2170],{},"document.body"," must defer the portal until after mount, because ",[349,2173,2174],{},"document"," does not exist during the RSC render pass.",[325,2177,2178,2179,2182,2183,2187],{},"Ensure lazy-loaded components maintain keyboard operability during fetch states. Suspense boundaries must not trap focus or strip interactive elements of their ",[349,2180,2181],{},"tabindex"," while resolving. The interaction between code splitting and keyboard order is subtle enough to warrant its own page; see ",[329,2184,2186],{"href":2185},"\u002Freact-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Fnextjs-dynamic-imports-and-keyboard-navigation\u002F","Next.js dynamic imports and keyboard navigation"," for the full pattern.",[325,2189,2190,2192,2193,2196,2197,2200],{},[339,2191,1244],{}," Audit the rendered component tree for missing ",[349,2194,2195],{},"role"," or ",[349,2198,2199],{},"aria-*"," attributes. Verify tab order consistency across deeply nested layouts and ensure no interactive elements are skipped during streaming resolution.",[394,2202],{},[397,2204,2206],{"id":2205},"streaming-suspense-and-loading-states","Streaming, Suspense, and Loading States",[325,2208,2209],{},"Streaming is the App Router's signature performance feature: the server flushes the shell immediately and fills Suspense boundaries as data resolves. For assistive technology, every flush is a silent DOM mutation. A screen reader user who started reading the shell may find that content has shifted beneath their cursor, or that a region they were about to reach has been replaced by real data with no notification.",[325,2211,2212,2213,2216,2217,623,2220,2223,2224,2227,2228,607],{},"Treat each Suspense boundary as a status region. The ",[349,2214,2215],{},"loading.tsx"," segment file gives you a route-level fallback that should communicate \"this area is loading\" through ",[349,2218,2219],{},"role=\"status\"",[349,2221,2222],{},"aria-busy",", not merely a spinning graphic. When the real content commits, the busy state clears and — if the change is significant enough to warrant it — a polite live region announces completion. Reserve ",[349,2225,2226],{},"aria-live=\"assertive\""," strictly for errors surfaced through ",[349,2229,2230],{},"error.tsx",[638,2232,2234],{"className":640,"code":2233,"language":642,"meta":643,"style":643},"\u002F\u002F app\u002Fdashboard\u002Floading.tsx\nexport default function Loading() {\n  return (\n    \u003Cdiv role=\"status\" aria-busy=\"true\" aria-live=\"polite\" className=\"route-loading\">\n      \u003Cspan className=\"sr-only\">Loading dashboard. Please wait.\u003C\u002Fspan>\n      \u003Cdiv className=\"skeleton\" aria-hidden=\"true\" \u002F>\n    \u003C\u002Fdiv>\n  );\n}\n",[349,2235,2236,2241,2254,2260,2294,2313,2335,2343,2347],{"__ignoreMap":643},[647,2237,2238],{"class":649,"line":650},[647,2239,2240],{"class":653},"\u002F\u002F app\u002Fdashboard\u002Floading.tsx\n",[647,2242,2243,2245,2247,2249,2252],{"class":649,"line":657},[647,2244,735],{"class":671},[647,2246,1337],{"class":671},[647,2248,738],{"class":671},[647,2250,2251],{"class":741}," Loading",[647,2253,745],{"class":664},[647,2255,2256,2258],{"class":649,"line":668},[647,2257,868],{"class":671},[647,2259,1172],{"class":664},[647,2261,2262,2264,2266,2268,2270,2272,2275,2277,2279,2281,2283,2285,2287,2289,2292],{"class":649,"line":686},[647,2263,1177],{"class":664},[647,2265,1181],{"class":1180},[647,2267,1208],{"class":741},[647,2269,1030],{"class":671},[647,2271,1213],{"class":660},[647,2273,2274],{"class":741}," aria-busy",[647,2276,1030],{"class":671},[647,2278,1197],{"class":660},[647,2280,1184],{"class":741},[647,2282,1030],{"class":671},[647,2284,1189],{"class":660},[647,2286,1200],{"class":741},[647,2288,1030],{"class":671},[647,2290,2291],{"class":660},"\"route-loading\"",[647,2293,1216],{"class":664},[647,2295,2296,2298,2300,2302,2304,2306,2309,2311],{"class":649,"line":701},[647,2297,1393],{"class":664},[647,2299,647],{"class":1180},[647,2301,1200],{"class":741},[647,2303,1030],{"class":671},[647,2305,1205],{"class":660},[647,2307,2308],{"class":664},">Loading dashboard. Please wait.\u003C\u002F",[647,2310,647],{"class":1180},[647,2312,1216],{"class":664},[647,2314,2315,2317,2319,2321,2323,2326,2329,2331,2333],{"class":649,"line":708},[647,2316,1393],{"class":664},[647,2318,1181],{"class":1180},[647,2320,1200],{"class":741},[647,2322,1030],{"class":671},[647,2324,2325],{"class":660},"\"skeleton\"",[647,2327,2328],{"class":741}," aria-hidden",[647,2330,1030],{"class":671},[647,2332,1197],{"class":660},[647,2334,1488],{"class":664},[647,2336,2337,2339,2341],{"class":649,"line":714},[647,2338,1226],{"class":664},[647,2340,1181],{"class":1180},[647,2342,1216],{"class":664},[647,2344,2345],{"class":649,"line":720},[647,2346,1235],{"class":664},[647,2348,2349],{"class":649,"line":726},[647,2350,877],{"class":664},[325,2352,2353,2354,2357,2358,2361,2362,2364],{},"A frequent mistake is to apply ",[349,2355,2356],{},"aria-busy=\"true\""," to a container that holds focusable elements while it is still loading. If a user tabs into that region mid-stream, they land on controls that may disappear when the real content commits, throwing focus back to ",[349,2359,2360],{},"\u003Cbody>",". Keep skeletons non-interactive: no ",[349,2363,2181],{},", no buttons, no links. The skeleton is a placeholder for sighted users; its accessible counterpart is the single status message.",[325,2366,2367,2369],{},[339,2368,1244],{}," Throttle the network to \"Slow 3G\" in DevTools and reload a streamed route. Confirm the status message is announced once on entry and that focus never lands inside a skeleton that later disappears.",[394,2371],{},[397,2373,2375],{"id":2374},"performance-optimization-accessibility-tradeoffs","Performance Optimization & Accessibility Tradeoffs",[325,2377,2378,2379,623,2381,2383],{},"Code-splitting, dynamic imports, and React Server Components streaming introduce latency that directly impacts assistive technology compatibility. Balance chunk splitting with predictable focus restoration. Use ",[349,2380,2215],{},[349,2382,2230],{}," route segments to provide accessible fallback states that communicate system status without relying on visual cues alone.",[325,2385,2386,2387,2389,2390,623,2392,2394],{},"Review proven patterns for ",[329,2388,2186],{"href":2185}," to implement safe lazy-loading. When streaming UI, implement ",[349,2391,2222],{},[349,2393,1274],{}," on container elements to prevent abrupt content jumps and ensure screen readers announce incremental updates gracefully.",[638,2396,2398],{"className":640,"code":2397,"language":642,"meta":643,"style":643},"\u002F\u002F app\u002Fdashboard\u002Fpage.tsx\nimport dynamic from \"next\u002Fdynamic\";\n\nconst HeavyDataGrid = dynamic(\n  () => import(\"@\u002Fcomponents\u002FHeavyDataGrid\"),\n  {\n    ssr: false,\n    loading: () => (\n      \u003Cdiv aria-live=\"polite\" aria-busy=\"true\" role=\"status\" className=\"loading-state\">\n        \u003Cspan className=\"sr-only\">Loading data grid. Please wait...\u003C\u002Fspan>\n        \u003Cdiv className=\"visual-spinner\" aria-hidden=\"true\" \u002F>\n      \u003C\u002Fdiv>\n    ),\n  }\n);\n\nexport default function DashboardPage() {\n  return (\n    \u003Csection aria-labelledby=\"grid-heading\">\n      \u003Ch2 id=\"grid-heading\">Analytics Overview\u003C\u002Fh2>\n      \u003CHeavyDataGrid \u002F>\n    \u003C\u002Fsection>\n  );\n}\n",[349,2399,2400,2405,2419,2423,2438,2456,2461,2472,2484,2517,2536,2557,2565,2570,2575,2579,2583,2596,2602,2619,2638,2647,2655,2659],{"__ignoreMap":643},[647,2401,2402],{"class":649,"line":650},[647,2403,2404],{"class":653},"\u002F\u002F app\u002Fdashboard\u002Fpage.tsx\n",[647,2406,2407,2409,2412,2414,2417],{"class":649,"line":657},[647,2408,672],{"class":671},[647,2410,2411],{"class":664}," dynamic ",[647,2413,678],{"class":671},[647,2415,2416],{"class":660}," \"next\u002Fdynamic\"",[647,2418,665],{"class":664},[647,2420,2421],{"class":649,"line":668},[647,2422,705],{"emptyLinePlaceholder":704},[647,2424,2425,2428,2431,2433,2436],{"class":649,"line":686},[647,2426,2427],{"class":671},"const",[647,2429,2430],{"class":754}," HeavyDataGrid",[647,2432,758],{"class":671},[647,2434,2435],{"class":741}," dynamic",[647,2437,1968],{"class":664},[647,2439,2440,2443,2445,2448,2450,2453],{"class":649,"line":701},[647,2441,2442],{"class":664},"  () ",[647,2444,809],{"class":671},[647,2446,2447],{"class":671}," import",[647,2449,1036],{"class":664},[647,2451,2452],{"class":660},"\"@\u002Fcomponents\u002FHeavyDataGrid\"",[647,2454,2455],{"class":664},"),\n",[647,2457,2458],{"class":649,"line":708},[647,2459,2460],{"class":664},"  {\n",[647,2462,2463,2466,2469],{"class":649,"line":714},[647,2464,2465],{"class":664},"    ssr: ",[647,2467,2468],{"class":754},"false",[647,2470,2471],{"class":664},",\n",[647,2473,2474,2477,2480,2482],{"class":649,"line":720},[647,2475,2476],{"class":741},"    loading",[647,2478,2479],{"class":664},": () ",[647,2481,809],{"class":671},[647,2483,1172],{"class":664},[647,2485,2486,2488,2490,2492,2494,2496,2498,2500,2502,2504,2506,2508,2510,2512,2515],{"class":649,"line":726},[647,2487,1393],{"class":664},[647,2489,1181],{"class":1180},[647,2491,1184],{"class":741},[647,2493,1030],{"class":671},[647,2495,1189],{"class":660},[647,2497,2274],{"class":741},[647,2499,1030],{"class":671},[647,2501,1197],{"class":660},[647,2503,1208],{"class":741},[647,2505,1030],{"class":671},[647,2507,1213],{"class":660},[647,2509,1200],{"class":741},[647,2511,1030],{"class":671},[647,2513,2514],{"class":660},"\"loading-state\"",[647,2516,1216],{"class":664},[647,2518,2519,2521,2523,2525,2527,2529,2532,2534],{"class":649,"line":732},[647,2520,1482],{"class":664},[647,2522,647],{"class":1180},[647,2524,1200],{"class":741},[647,2526,1030],{"class":671},[647,2528,1205],{"class":660},[647,2530,2531],{"class":664},">Loading data grid. Please wait...\u003C\u002F",[647,2533,647],{"class":1180},[647,2535,1216],{"class":664},[647,2537,2538,2540,2542,2544,2546,2549,2551,2553,2555],{"class":649,"line":748},[647,2539,1482],{"class":664},[647,2541,1181],{"class":1180},[647,2543,1200],{"class":741},[647,2545,1030],{"class":671},[647,2547,2548],{"class":660},"\"visual-spinner\"",[647,2550,2328],{"class":741},[647,2552,1030],{"class":671},[647,2554,1197],{"class":660},[647,2556,1488],{"class":664},[647,2558,2559,2561,2563],{"class":649,"line":767},[647,2560,1421],{"class":664},[647,2562,1181],{"class":1180},[647,2564,1216],{"class":664},[647,2566,2567],{"class":649,"line":795},[647,2568,2569],{"class":664},"    ),\n",[647,2571,2572],{"class":649,"line":800},[647,2573,2574],{"class":664},"  }\n",[647,2576,2577],{"class":649,"line":815},[647,2578,792],{"class":664},[647,2580,2581],{"class":649,"line":824},[647,2582,705],{"emptyLinePlaceholder":704},[647,2584,2585,2587,2589,2591,2594],{"class":649,"line":830},[647,2586,735],{"class":671},[647,2588,1337],{"class":671},[647,2590,738],{"class":671},[647,2592,2593],{"class":741}," DashboardPage",[647,2595,745],{"class":664},[647,2597,2598,2600],{"class":649,"line":848},[647,2599,868],{"class":671},[647,2601,1172],{"class":664},[647,2603,2604,2606,2609,2612,2614,2617],{"class":649,"line":854},[647,2605,1177],{"class":664},[647,2607,2608],{"class":1180},"section",[647,2610,2611],{"class":741}," aria-labelledby",[647,2613,1030],{"class":671},[647,2615,2616],{"class":660},"\"grid-heading\"",[647,2618,1216],{"class":664},[647,2620,2621,2623,2625,2627,2629,2631,2634,2636],{"class":649,"line":860},[647,2622,1393],{"class":664},[647,2624,397],{"class":1180},[647,2626,1115],{"class":741},[647,2628,1030],{"class":671},[647,2630,2616],{"class":660},[647,2632,2633],{"class":664},">Analytics Overview\u003C\u002F",[647,2635,397],{"class":1180},[647,2637,1216],{"class":664},[647,2639,2640,2642,2645],{"class":649,"line":865},[647,2641,1393],{"class":664},[647,2643,2644],{"class":754},"HeavyDataGrid",[647,2646,1488],{"class":664},[647,2648,2649,2651,2653],{"class":649,"line":874},[647,2650,1226],{"class":664},[647,2652,2608],{"class":1180},[647,2654,1216],{"class":664},[647,2656,2657],{"class":649,"line":880},[647,2658,1235],{"class":664},[647,2660,2661],{"class":649,"line":885},[647,2662,877],{"class":664},[325,2664,2665,2666,2669,2670,2672],{},"There is a real tradeoff between aggressive ",[349,2667,2668],{},"ssr: false"," splitting and screen reader robustness. Disabling server rendering means the component is absent from the initial accessibility tree entirely; a screen reader that begins reading before hydration completes will encounter only the fallback. For content that is part of the page's primary reading order, prefer server-rendered components with Suspense streaming over ",[349,2671,2668],{},", and reserve client-only dynamic imports for genuinely interactive widgets (charts, editors, maps) where the loading state is a legitimate, communicable status.",[325,2674,2675,2677,2678,2680],{},[339,2676,1244],{}," Simulate slow 3G networks using browser DevTools. Test screen reader announcements during streaming and dynamic import resolution. Verify that ",[349,2679,2222],{}," toggles correctly and that focus is not lost when the heavy component mounts.",[394,2682],{},[397,2684,2686],{"id":2685},"verifying-app-router-accessibility","Verifying App Router Accessibility",[325,2688,2689],{},"No single tool catches every App Router defect, because the failures are temporal — they occur during transitions and streaming, not in a static snapshot. Combine automated scanning with scripted interaction:",[343,2691,2692,2716,2726,2732],{},[346,2693,2694,2697,2698,2701,2702,2196,2705,2708,2709,2712,2713,607],{},[339,2695,2696],{},"Automated, static:"," Run ",[349,2699,2700],{},"axe-core"," (via ",[349,2703,2704],{},"@axe-core\u002Fplaywright",[349,2706,2707],{},"jest-axe",") against rendered routes to catch missing landmarks, labels, and contrast issues. This catches injection-point omissions like a ",[349,2710,2711],{},"\u003Cmain>"," without ",[349,2714,2715],{},"tabIndex={-1}",[346,2717,2718,2721,2722,2725],{},[339,2719,2720],{},"Automated, behavioral:"," Use Playwright to drive a real navigation, then assert ",[349,2723,2724],{},"document.activeElement"," is the new heading and that the route announcer's text content updated. This is the only reliable way to catch a missing focus shift.",[346,2727,2728,2731],{},[339,2729,2730],{},"Manual, screen reader:"," Navigate the app with NVDA (Windows) or VoiceOver (macOS) using only the keyboard. Confirm each route change announces the new page and that streamed regions announce on completion rather than mutating silently.",[346,2733,2734,2737,2738,2741],{},[339,2735,2736],{},"Manual, keyboard:"," Tab through every route with the mouse unplugged. Verify the skip link is first, focus never disappears into a skeleton, and ",[349,2739,2740],{},":focus-visible"," styling survives across nested layouts.",[325,2743,2744],{},"A green axe report on a static render is necessary but never sufficient for the App Router; the behavioral Playwright assertion is what proves the transition logic actually fires.",[394,2746],{},[397,2748,2750],{"id":2749},"common-pitfalls","Common Pitfalls",[343,2752,2753,2759,2765,2774,2783,2795],{},[346,2754,2755,2758],{},[339,2756,2757],{},"Assuming automatic focus management:"," The App Router does not automatically reset focus on route transitions. Manual implementation is required.",[346,2760,2761,2764],{},[339,2762,2763],{},"Hydration mismatches from misplaced hooks:"," Placing interactive accessibility hooks inside Server Components triggers hydration errors and breaks state synchronization.",[346,2766,2767,2773],{},[339,2768,2769,2770,2772],{},"Overusing ",[349,2771,1274],{}," regions:"," Excessive live regions cause screen reader verbosity, interrupting navigation and creating conflicting announcements.",[346,2775,2776,2779,2780,2782],{},[339,2777,2778],{},"Neglecting heading structure during intercepting routes:"," Failing to update ",[349,2781,630],{}," and heading hierarchy breaks context for assistive technology.",[346,2784,2785,2788,2789,2791,2792,2794],{},[339,2786,2787],{},"Broken focus indicators across layout boundaries:"," Using ",[349,2790,406],{}," without preserving visible ",[349,2793,2740],{}," styles across nested layouts or portals.",[346,2796,2797,2800,2801,2803],{},[339,2798,2799],{},"Focusable skeletons:"," Leaving ",[349,2802,2181],{}," or interactive elements inside a Suspense fallback, so focus lands on controls that vanish when real content commits.",[394,2805],{},[397,2807,2809],{"id":2808},"key-takeaways","Key Takeaways",[343,2811,2812,2815,2818,2821,2827],{},[346,2813,2814],{},"App Router navigation is a silent DOM swap. Reconstruct the browser's lost behavior by injecting a focus shift and a polite announcement at the layout level.",[346,2816,2817],{},"Split responsibilities by intent: descriptive semantics on the server, reactive interactivity on the client. This keeps bundles small without losing the accessibility tree.",[346,2819,2820],{},"Treat every Suspense boundary as a status region and keep skeletons strictly non-interactive.",[346,2822,2823,2824,2826],{},"Prefer streamed server rendering over ",[349,2825,2668],{}," for content in the primary reading order.",[346,2828,2829],{},"Verification requires behavioral tests; static axe scans cannot prove that transition logic fires.",[394,2831],{},[397,2833,2835],{"id":2834},"frequently-asked-questions","Frequently Asked Questions",[325,2837,2838,2841,2842,1021,2844,2846],{},[339,2839,2840],{},"Does Next.js App Router automatically handle focus management on route changes?","\nNo. Unlike traditional SPAs, the App Router's server-driven navigation does not automatically reset focus. Developers must implement manual focus management using ",[349,2843,622],{},[349,2845,626],{},", and refs to target the main content heading after navigation.",[325,2848,2849,2852,2853,2196,2856,2858],{},[339,2850,2851],{},"Can I use React accessibility hooks in Server Components?","\nNo. Server Components cannot use hooks, event listeners, or browser APIs like ",[349,2854,2855],{},"window",[349,2857,2174],{},". All interactive a11y logic, including hooks for focus management and live regions, must be isolated within Client Components.",[325,2860,2861,2864,2865,2868,2869,2871],{},[339,2862,2863],{},"How do I handle accessibility with parallel routes in the App Router?","\nParallel routes render multiple independent UI trees. You must manage focus context per route segment and ensure screen readers announce changes without conflicting announcements. Use ",[349,2866,2867],{},"aria-owns"," or explicit ",[349,2870,2159],{}," to associate parallel content with its controlling navigation.",[325,2873,2874,2880,2881,2883],{},[339,2875,2876,2877,2879],{},"Should I disable server rendering with ",[349,2878,2668],{}," for accessibility?","\nOnly for genuinely interactive, client-only widgets. Content in the page's primary reading order should be server-rendered and streamed via Suspense, because ",[349,2882,2668],{}," removes it from the initial accessibility tree until hydration completes.",[325,2885,2886,2892,2893,2896],{},[339,2887,2888,2889,2891],{},"Why does my ",[349,2890,1274],{}," region announce on first page load?","\nBecause the region writes its message on the same render that mounts it. Skip the initial mount with a ",[349,2894,2895],{},"useRef"," flag so the live region only announces subsequent client-side navigations, not the load the browser already announced.",[325,2898,2899,2902,2903,2905],{},[339,2900,2901],{},"How do I verify the App Router focus shift actually works?","\nStatic axe scans cannot detect it. Drive a real navigation in Playwright and assert that ",[349,2904,2724],{}," is the new page heading after the transition, alongside manual screen reader testing.",[394,2907],{},[397,2909,2911],{"id":2910},"related-guides","Related guides",[343,2913,2914,2918,2922,2926],{},[346,2915,2916],{},[329,2917,94],{"href":331},[346,2919,2920],{},[329,2921,635],{"href":634},[346,2923,2924],{},[329,2925,2186],{"href":2185},[346,2927,2928],{},[329,2929,2930],{"href":1278},"Server Components and client-side interactivity",[2932,2933,2934],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .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}",{"title":643,"searchDepth":657,"depth":657,"links":2936},[2937,2938,2939,2940,2941,2942,2943,2944,2945,2946,2947],{"id":399,"depth":657,"text":400},{"id":612,"depth":657,"text":613},{"id":1257,"depth":657,"text":1258},{"id":2138,"depth":657,"text":2139},{"id":2205,"depth":657,"text":2206},{"id":2374,"depth":657,"text":2375},{"id":2685,"depth":657,"text":2686},{"id":2749,"depth":657,"text":2750},{"id":2808,"depth":657,"text":2809},{"id":2834,"depth":657,"text":2835},{"id":2910,"depth":657,"text":2911},null,"Implement accessibility correctly in the Next.js App Router with practical guidance for focus behavior, route transitions, streaming UI, and semantic integrity.","md",{},false,{"title":163,"description":2949},"q3DNX6UIACAsaroIZ2qRBAcnVWqjM38Pp4P5803bTHA",[2956,2995,2996,3059],{"title":5,"path":6,"stem":7,"children":2957,"page":-1},[2958,2959,2962,2965,2971,2977,2986,2992],{"title":10,"path":6,"stem":11},{"title":13,"path":14,"stem":15,"children":2960},[2961],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":2963},[2964],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":2966,"page":-1},[2967,2968],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":2969},[2970],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":2972,"page":-1},[2973,2974],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":2975},[2976],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":2978,"page":-1},[2979,2980,2983],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":2981},[2982],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":2984},[2985],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":2987},[2988,2989],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":2990},[2991],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":2993},[2994],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87},{"title":89,"path":90,"stem":91,"children":2997,"page":-1},[2998,2999,3005,3017,3029,3032,3041,3053],{"title":94,"path":90,"stem":95},{"title":97,"path":98,"stem":99,"children":3000,"page":-1},[3001,3002],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":3003},[3004],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":3006,"page":-1},[3007,3008,3011,3014],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":3009},[3010],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":3012},[3013],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":3015},[3016],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":3018,"page":-1},[3019,3020,3023,3026],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":3021},[3022],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":3024},[3025],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":3027},[3028],{"title":151,"path":152,"stem":153},{"title":157,"path":158,"stem":159,"children":3030},[3031],{"title":157,"path":158,"stem":159},{"title":163,"path":164,"stem":165,"children":3033,"page":-1},[3034,3035,3038],{"title":163,"path":164,"stem":165},{"title":169,"path":170,"stem":171,"children":3036},[3037],{"title":169,"path":170,"stem":171},{"title":175,"path":176,"stem":177,"children":3039},[3040],{"title":175,"path":176,"stem":177},{"title":181,"path":182,"stem":183,"children":3042,"page":-1},[3043,3044,3047,3050],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189,"children":3045},[3046],{"title":187,"path":188,"stem":189},{"title":193,"path":194,"stem":195,"children":3048},[3049],{"title":193,"path":194,"stem":195},{"title":199,"path":200,"stem":201,"children":3051},[3052],{"title":199,"path":200,"stem":201},{"title":205,"path":206,"stem":207,"children":3054,"page":-1},[3055,3056],{"title":205,"path":206,"stem":207},{"title":211,"path":212,"stem":213,"children":3057},[3058],{"title":211,"path":212,"stem":213},{"title":217,"path":218,"stem":219,"children":3060,"page":-1},[3061,3062,3071,3080,3089,3098],{"title":222,"path":218,"stem":223},{"title":225,"path":226,"stem":227,"children":3063},[3064,3065,3068],{"title":225,"path":226,"stem":227},{"title":231,"path":232,"stem":233,"children":3066},[3067],{"title":231,"path":232,"stem":233},{"title":237,"path":238,"stem":239,"children":3069},[3070],{"title":237,"path":238,"stem":239},{"title":243,"path":244,"stem":245,"children":3072,"page":-1},[3073,3074,3077],{"title":243,"path":244,"stem":245},{"title":249,"path":250,"stem":251,"children":3075},[3076],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257,"children":3078},[3079],{"title":255,"path":256,"stem":257},{"title":261,"path":262,"stem":263,"children":3081,"page":-1},[3082,3083,3086],{"title":261,"path":262,"stem":263},{"title":267,"path":268,"stem":269,"children":3084},[3085],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275,"children":3087},[3088],{"title":273,"path":274,"stem":275},{"title":279,"path":280,"stem":281,"children":3090,"page":-1},[3091,3092,3095],{"title":279,"path":280,"stem":281},{"title":285,"path":286,"stem":287,"children":3093},[3094],{"title":285,"path":286,"stem":287},{"title":291,"path":292,"stem":293,"children":3096},[3097],{"title":291,"path":292,"stem":293},{"title":297,"path":298,"stem":299,"children":3099,"page":-1},[3100,3101,3104],{"title":297,"path":298,"stem":299},{"title":303,"path":304,"stem":305,"children":3102},[3103],{"title":303,"path":304,"stem":305},{"title":309,"path":310,"stem":311,"children":3105},[3106],{"title":309,"path":310,"stem":311},1781785523210]