[{"data":1,"prerenderedAt":1196},["ShallowReactive",2],{"site-header-nav":3,"page-\u002Fcore-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas\u002Fhandling-focus-restoration-after-dynamic-route-changes\u002F":156,"content-navigation":1122},[4,66,70],{"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,60],{"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 in Modern Frameworks","\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},"Screen Reader Compatibility Testing for Modern Frameworks","\u002Fcore-accessibility-principles-for-modern-frameworks\u002Fscreen-reader-compatibility-testing","core-accessibility-principles-for-modern-frameworks\u002Fscreen-reader-compatibility-testing\u002Findex",[53,54],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":58},"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",[59],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":64},"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",[65],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69},"Modern Framework Accessibility","\u002F","index",{"title":71,"path":72,"stem":73,"children":74},"React Nextjs Accessibility Patterns","\u002Freact-nextjs-accessibility-patterns","react-nextjs-accessibility-patterns",[75,78,90,102,108,126,144],{"title":76,"path":72,"stem":77},"React & Next.js Accessibility Patterns","react-nextjs-accessibility-patterns\u002Findex",{"title":79,"path":80,"stem":81,"children":82},"Accessible Component Libraries in React","\u002Freact-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react","react-nextjs-accessibility-patterns\u002Faccessible-component-libraries-in-react\u002Findex",[83,84],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87,"children":88},"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",[89],{"title":85,"path":86,"stem":87},{"title":91,"path":92,"stem":93,"children":94},"Dynamic Content & State Announcements in React & Next.js","\u002Freact-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements","react-nextjs-accessibility-patterns\u002Fdynamic-content-state-announcements\u002Findex",[95,96],{"title":91,"path":92,"stem":93},{"title":97,"path":98,"stem":99,"children":100},"Implementing React Context for Global 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",[101],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":106},"Form Handling with React Hook Form & Accessibility","\u002Freact-nextjs-accessibility-patterns\u002Fform-handling-with-react-hook-form-a11y","react-nextjs-accessibility-patterns\u002Fform-handling-with-react-hook-form-a11y\u002Findex",[107],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":112},"Next.js App Router & A11y: Implementation Guide for Modern Frameworks","\u002Freact-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y","react-nextjs-accessibility-patterns\u002Fnextjs-app-router-a11y\u002Findex",[113,114,120],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":118},"Implementing Skip Links in Next.js App Router: A Step-by-Step Guide","\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",[119],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":124},"Next.js Dynamic Imports and Keyboard Navigation: A Complete A11y Implementation Guide","\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",[125],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":130},"React Hooks for Accessibility: Implementation Patterns & State Management","\u002Freact-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility","react-nextjs-accessibility-patterns\u002Freact-hooks-for-accessibility\u002Findex",[131,132,138],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":136},"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",[137],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":142},"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",[143],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":148},"Server Components & Client-Side Interactivity","\u002Freact-nextjs-accessibility-patterns\u002Fserver-components-client-side-interactivity","react-nextjs-accessibility-patterns\u002Fserver-components-client-side-interactivity\u002Findex",[149,150],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":154},"Handling 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",[155],{"title":151,"path":152,"stem":153},{"id":157,"title":31,"body":158,"date":1115,"description":1116,"extension":1117,"image":1115,"meta":1118,"modifiedAt":1115,"navigation":423,"noindex":1119,"path":32,"publishedAt":1115,"seo":1120,"stem":33,"updatedAt":1115,"__hash__":1121},"content\u002Fcore-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas\u002Fhandling-focus-restoration-after-dynamic-route-changes\u002Findex.md",{"type":159,"value":160,"toc":1108},"minimark",[161,165,178,184,204,209,223,228,231,236,274,279,316,320,327,331,360,365,371,644,649,780,785,814,818,821,825,844,848,964,968,990,994,1056,1060,1073,1083,1104],[162,163,31],"h1",{"id":164},"handling-focus-restoration-after-dynamic-route-changes",[166,167,168,169,173,174,177],"p",{},"When Single Page Applications (SPAs) update the DOM without a full page reload, assistive technologies frequently lose context. Properly restoring focus to a logical landmark or heading after navigation is critical for keyboard and screen reader users. This implementation pattern aligns with established ",[170,171,10],"a",{"href":172},"\u002Fcore-accessibility-principles-for-modern-frameworks\u002F"," and integrates directly into broader ",[170,175,25],{"href":176},"\u002Fcore-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas\u002F",".",[166,179,180],{},[181,182,183],"strong",{},"WCAG Compliance Targets:",[185,186,187,194,199],"ul",{},[188,189,190],"li",{},[191,192,193],"code",{},"2.4.3 Focus Order (Level A)",[188,195,196],{},[191,197,198],{},"3.2.3 Consistent Navigation (Level AA)",[188,200,201],{},[191,202,203],{},"4.1.2 Name, Role, Value (Level A)",[166,205,206],{},[181,207,208],{},"Implementation Objectives:",[185,210,211,214,217,220],{},[188,212,213],{},"Detect route completion lifecycle events",[188,215,216],{},"Identify logical focus targets (main heading, skip link, or primary landmark)",[188,218,219],{},"Execute programmatic focus with scroll alignment",[188,221,222],{},"Provide accessible announcements for screen readers",[224,225,227],"h2",{"id":226},"intercepting-route-change-lifecycle-events","Intercepting Route Change Lifecycle Events",[166,229,230],{},"Framework routers expose navigation lifecycle hooks that must be leveraged to guarantee DOM stability before focus manipulation. Focus logic must execute strictly after the new route's component tree mounts and paints.",[166,232,233],{},[181,234,235],{},"Implementation Steps:",[237,238,239,258,268],"ol",{},[188,240,241,242,245,246,249,250,253,254,257],{},"Bind to post-navigation hooks (",[191,243,244],{},"afterEach"," in Vue Router, ",[191,247,248],{},"useEffect"," dependency on ",[191,251,252],{},"location"," in React Router, or ",[191,255,256],{},"NavigationEnd"," in Angular Router).",[188,259,260,261,264,265,177],{},"Defer execution until the browser completes the paint cycle using ",[191,262,263],{},"requestAnimationFrame"," or ",[191,266,267],{},"setTimeout(..., 0)",[188,269,270,271,177],{},"Validate target element existence via DOM query before invoking ",[191,272,273],{},".focus()",[166,275,276],{},[181,277,278],{},"Debugging & Testing Workflow:",[185,280,281,288,291,302],{},[188,282,283,284,287],{},"Attach a ",[191,285,286],{},"console.count()"," to the navigation hook to verify it fires exactly once per route transition.",[188,289,290],{},"Ensure the hook does not trigger on internal component state updates, modal toggles, or client-side data refetches.",[188,292,293,294,297,298,301],{},"Use browser DevTools Performance tab to confirm focus execution occurs after ",[191,295,296],{},"Layout"," and ",[191,299,300],{},"Paint"," events.",[188,303,304,305,264,308,311,312,315],{},"In CI pipelines, run ",[191,306,307],{},"axe-core",[191,309,310],{},"pa11y"," against route snapshots to detect missing focus targets or orphaned ",[191,313,314],{},"tabindex"," attributes.",[224,317,319],{"id":318},"programmatic-focus-execution-scroll-alignment","Programmatic Focus Execution & Scroll Alignment",[166,321,322,323,326],{},"Focus must land on a semantic container that represents the new view. Non-interactive elements require ",[191,324,325],{},"tabindex=\"-1\""," to accept programmatic focus without entering the sequential keyboard tab order.",[166,328,329],{},[181,330,235],{},[237,332,333,347,353],{},[188,334,335,336,339,340,343,344,346],{},"Query the primary content landmark (",[191,337,338],{},"\u003Cmain>",", ",[191,341,342],{},"[role=\"main\"]",", or route-specific ",[191,345,162],{},").",[188,348,349,350,352],{},"Apply ",[191,351,325],{}," dynamically if the element lacks native focusability.",[188,354,355,356,359],{},"Invoke ",[191,357,358],{},"element.focus({ preventScroll: false })"," to guarantee the viewport scrolls to the focused element.",[166,361,362],{},[181,363,364],{},"Code Implementation:",[166,366,367],{},[368,369,370],"em",{},"React Router Focus Restoration Hook",[372,373,378],"pre",{"className":374,"code":375,"language":376,"meta":377,"style":377},"language-javascript shiki shiki-themes github-light github-dark","import { useEffect } from 'react';\nimport { useLocation } from 'react-router-dom';\n\nexport function useRouteFocus() {\n const location = useLocation();\n\n useEffect(() => {\n \u002F\u002F Defer until DOM paint completes\n const timer = setTimeout(() => {\n const target = document.querySelector('main') || document.querySelector('[role=\"main\"]');\n if (target) {\n target.setAttribute('tabindex', '-1');\n target.focus({ preventScroll: false });\n }\n }, 100);\n\n return () => clearTimeout(timer);\n }, [location.pathname]);\n}\n","javascript","",[191,379,380,403,418,425,441,460,465,480,487,506,546,555,576,593,599,610,615,632,638],{"__ignoreMap":377},[381,382,385,389,393,396,400],"span",{"class":383,"line":384},"line",1,[381,386,388],{"class":387},"szBVR","import",[381,390,392],{"class":391},"sVt8B"," { useEffect } ",[381,394,395],{"class":387},"from",[381,397,399],{"class":398},"sZZnC"," 'react'",[381,401,402],{"class":391},";\n",[381,404,406,408,411,413,416],{"class":383,"line":405},2,[381,407,388],{"class":387},[381,409,410],{"class":391}," { useLocation } ",[381,412,395],{"class":387},[381,414,415],{"class":398}," 'react-router-dom'",[381,417,402],{"class":391},[381,419,421],{"class":383,"line":420},3,[381,422,424],{"emptyLinePlaceholder":423},true,"\n",[381,426,428,431,434,438],{"class":383,"line":427},4,[381,429,430],{"class":387},"export",[381,432,433],{"class":387}," function",[381,435,437],{"class":436},"sScJk"," useRouteFocus",[381,439,440],{"class":391},"() {\n",[381,442,444,447,451,454,457],{"class":383,"line":443},5,[381,445,446],{"class":387}," const",[381,448,450],{"class":449},"sj4cs"," location",[381,452,453],{"class":387}," =",[381,455,456],{"class":436}," useLocation",[381,458,459],{"class":391},"();\n",[381,461,463],{"class":383,"line":462},6,[381,464,424],{"emptyLinePlaceholder":423},[381,466,468,471,474,477],{"class":383,"line":467},7,[381,469,470],{"class":436}," useEffect",[381,472,473],{"class":391},"(() ",[381,475,476],{"class":387},"=>",[381,478,479],{"class":391}," {\n",[381,481,483],{"class":383,"line":482},8,[381,484,486],{"class":485},"sJ8bj"," \u002F\u002F Defer until DOM paint completes\n",[381,488,490,492,495,497,500,502,504],{"class":383,"line":489},9,[381,491,446],{"class":387},[381,493,494],{"class":449}," timer",[381,496,453],{"class":387},[381,498,499],{"class":436}," setTimeout",[381,501,473],{"class":391},[381,503,476],{"class":387},[381,505,479],{"class":391},[381,507,509,511,514,516,519,522,525,528,531,534,536,538,540,543],{"class":383,"line":508},10,[381,510,446],{"class":387},[381,512,513],{"class":449}," target",[381,515,453],{"class":387},[381,517,518],{"class":391}," document.",[381,520,521],{"class":436},"querySelector",[381,523,524],{"class":391},"(",[381,526,527],{"class":398},"'main'",[381,529,530],{"class":391},") ",[381,532,533],{"class":387},"||",[381,535,518],{"class":391},[381,537,521],{"class":436},[381,539,524],{"class":391},[381,541,542],{"class":398},"'[role=\"main\"]'",[381,544,545],{"class":391},");\n",[381,547,549,552],{"class":383,"line":548},11,[381,550,551],{"class":387}," if",[381,553,554],{"class":391}," (target) {\n",[381,556,558,561,564,566,569,571,574],{"class":383,"line":557},12,[381,559,560],{"class":391}," target.",[381,562,563],{"class":436},"setAttribute",[381,565,524],{"class":391},[381,567,568],{"class":398},"'tabindex'",[381,570,339],{"class":391},[381,572,573],{"class":398},"'-1'",[381,575,545],{"class":391},[381,577,579,581,584,587,590],{"class":383,"line":578},13,[381,580,560],{"class":391},[381,582,583],{"class":436},"focus",[381,585,586],{"class":391},"({ preventScroll: ",[381,588,589],{"class":449},"false",[381,591,592],{"class":391}," });\n",[381,594,596],{"class":383,"line":595},14,[381,597,598],{"class":391}," }\n",[381,600,602,605,608],{"class":383,"line":601},15,[381,603,604],{"class":391}," }, ",[381,606,607],{"class":449},"100",[381,609,545],{"class":391},[381,611,613],{"class":383,"line":612},16,[381,614,424],{"emptyLinePlaceholder":423},[381,616,618,621,624,626,629],{"class":383,"line":617},17,[381,619,620],{"class":387}," return",[381,622,623],{"class":391}," () ",[381,625,476],{"class":387},[381,627,628],{"class":436}," clearTimeout",[381,630,631],{"class":391},"(timer);\n",[381,633,635],{"class":383,"line":634},18,[381,636,637],{"class":391}," }, [location.pathname]);\n",[381,639,641],{"class":383,"line":640},19,[381,642,643],{"class":391},"}\n",[166,645,646],{},[368,647,648],{},"Vue Router Global Focus Guard",[372,650,652],{"className":374,"code":651,"language":376,"meta":377,"style":377},"router.afterEach((to, from) => {\n Vue.nextTick(() => {\n const heading = document.querySelector(`#${to.name}-heading`) || document.querySelector('h1');\n if (heading) {\n heading.setAttribute('tabindex', '-1');\n heading.focus();\n }\n });\n});\n",[191,653,654,678,692,735,742,759,767,771,775],{"__ignoreMap":377},[381,655,656,659,661,664,668,670,672,674,676],{"class":383,"line":384},[381,657,658],{"class":391},"router.",[381,660,244],{"class":436},[381,662,663],{"class":391},"((",[381,665,667],{"class":666},"s4XuR","to",[381,669,339],{"class":391},[381,671,395],{"class":666},[381,673,530],{"class":391},[381,675,476],{"class":387},[381,677,479],{"class":391},[381,679,680,683,686,688,690],{"class":383,"line":405},[381,681,682],{"class":391}," Vue.",[381,684,685],{"class":436},"nextTick",[381,687,473],{"class":391},[381,689,476],{"class":387},[381,691,479],{"class":391},[381,693,694,696,699,701,703,705,707,710,712,714,717,720,722,724,726,728,730,733],{"class":383,"line":420},[381,695,446],{"class":387},[381,697,698],{"class":449}," heading",[381,700,453],{"class":387},[381,702,518],{"class":391},[381,704,521],{"class":436},[381,706,524],{"class":391},[381,708,709],{"class":398},"`#${",[381,711,667],{"class":391},[381,713,177],{"class":398},[381,715,716],{"class":391},"name",[381,718,719],{"class":398},"}-heading`",[381,721,530],{"class":391},[381,723,533],{"class":387},[381,725,518],{"class":391},[381,727,521],{"class":436},[381,729,524],{"class":391},[381,731,732],{"class":398},"'h1'",[381,734,545],{"class":391},[381,736,737,739],{"class":383,"line":427},[381,738,551],{"class":387},[381,740,741],{"class":391}," (heading) {\n",[381,743,744,747,749,751,753,755,757],{"class":383,"line":443},[381,745,746],{"class":391}," heading.",[381,748,563],{"class":436},[381,750,524],{"class":391},[381,752,568],{"class":398},[381,754,339],{"class":391},[381,756,573],{"class":398},[381,758,545],{"class":391},[381,760,761,763,765],{"class":383,"line":462},[381,762,746],{"class":391},[381,764,583],{"class":436},[381,766,459],{"class":391},[381,768,769],{"class":383,"line":467},[381,770,598],{"class":391},[381,772,773],{"class":383,"line":482},[381,774,592],{"class":391},[381,776,777],{"class":383,"line":489},[381,778,779],{"class":391},"});\n",[166,781,782],{},[181,783,784],{},"Testing Workflow:",[185,786,787,797,800,807],{},[188,788,789,790,297,793,796],{},"Navigate using ",[191,791,792],{},"Tab",[191,794,795],{},"Shift+Tab"," to verify the focus ring is visible on the target element.",[188,798,799],{},"Confirm scroll position aligns with the focused element without layout shift or animation jank.",[188,801,802,803,806],{},"Validate against ",[191,804,805],{},"prefers-reduced-motion"," settings to ensure scroll behavior remains predictable.",[188,808,809,810,813],{},"Execute E2E tests (Cypress\u002FPlaywright) simulating keyboard-only navigation to assert ",[191,811,812],{},"document.activeElement"," matches the expected route landmark.",[224,815,817],{"id":816},"accessible-route-change-announcements","Accessible Route Change Announcements",[166,819,820],{},"Screen readers require explicit notification of route changes. Announcements must be decoupled from focus movement to prevent speech queue collisions.",[166,822,823],{},[181,824,235],{},[237,826,827,834,841],{},[188,828,829,830,833],{},"Implement a persistent ",[191,831,832],{},"aria-live=\"polite\""," region at the document root.",[188,835,836,837,840],{},"Inject concise route metadata (e.g., ",[191,838,839],{},"Page loaded: ${routeName}",") only after focus restoration completes.",[188,842,843],{},"Clear the live region text after announcement to prevent duplicate reads on subsequent navigations.",[166,845,846],{},[181,847,364],{},[372,849,851],{"className":374,"code":850,"language":376,"meta":377,"style":377},"\u002F\u002F Live region utility\nexport function announceRouteChange(routeName) {\n const liveRegion = document.getElementById('route-announcer');\n if (liveRegion) {\n liveRegion.textContent = ''; \u002F\u002F Clear previous\n \u002F\u002F Force DOM update before setting new text\n requestAnimationFrame(() => {\n liveRegion.textContent = `Page loaded: ${routeName}`;\n });\n }\n}\n",[191,852,853,858,875,896,903,920,925,936,952,956,960],{"__ignoreMap":377},[381,854,855],{"class":383,"line":384},[381,856,857],{"class":485},"\u002F\u002F Live region utility\n",[381,859,860,862,864,867,869,872],{"class":383,"line":405},[381,861,430],{"class":387},[381,863,433],{"class":387},[381,865,866],{"class":436}," announceRouteChange",[381,868,524],{"class":391},[381,870,871],{"class":666},"routeName",[381,873,874],{"class":391},") {\n",[381,876,877,879,882,884,886,889,891,894],{"class":383,"line":420},[381,878,446],{"class":387},[381,880,881],{"class":449}," liveRegion",[381,883,453],{"class":387},[381,885,518],{"class":391},[381,887,888],{"class":436},"getElementById",[381,890,524],{"class":391},[381,892,893],{"class":398},"'route-announcer'",[381,895,545],{"class":391},[381,897,898,900],{"class":383,"line":427},[381,899,551],{"class":387},[381,901,902],{"class":391}," (liveRegion) {\n",[381,904,905,908,911,914,917],{"class":383,"line":443},[381,906,907],{"class":391}," liveRegion.textContent ",[381,909,910],{"class":387},"=",[381,912,913],{"class":398}," ''",[381,915,916],{"class":391},"; ",[381,918,919],{"class":485},"\u002F\u002F Clear previous\n",[381,921,922],{"class":383,"line":462},[381,923,924],{"class":485}," \u002F\u002F Force DOM update before setting new text\n",[381,926,927,930,932,934],{"class":383,"line":467},[381,928,929],{"class":436}," requestAnimationFrame",[381,931,473],{"class":391},[381,933,476],{"class":387},[381,935,479],{"class":391},[381,937,938,940,942,945,947,950],{"class":383,"line":482},[381,939,907],{"class":391},[381,941,910],{"class":387},[381,943,944],{"class":398}," `Page loaded: ${",[381,946,871],{"class":391},[381,948,949],{"class":398},"}`",[381,951,402],{"class":391},[381,953,954],{"class":383,"line":489},[381,955,592],{"class":391},[381,957,958],{"class":383,"line":508},[381,959,598],{"class":391},[381,961,962],{"class":383,"line":548},[381,963,643],{"class":391},[166,965,966],{},[181,967,784],{},[185,969,970,973,976,983],{},[188,971,972],{},"Validate with VoiceOver (macOS\u002FiOS) and NVDA (Windows) to ensure announcements queue politely.",[188,974,975],{},"Confirm announcements do not interrupt ongoing speech or duplicate focus cues.",[188,977,978,979,982],{},"Verify the live region remains visually hidden (",[191,980,981],{},"position: absolute; clip: rect(0,0,0,0);",") but accessible to AT.",[188,984,985,986,989],{},"Monitor console for ",[191,987,988],{},"aria-live"," region duplication or race conditions during rapid route transitions.",[224,991,993],{"id":992},"common-implementation-pitfalls","Common Implementation Pitfalls",[185,995,996,1005,1024,1037,1046],{},[188,997,998,1001,1002,1004],{},[181,999,1000],{},"Premature Focus Execution:"," Invoking ",[191,1003,273],{}," before the router finishes rendering the new view results in silent failures or focus snapping to the previous route.",[188,1006,1007,1013,1014,339,1017,1020,1021,1023],{},[181,1008,1009,1010,1012],{},"Missing ",[191,1011,325],{},":"," Targeting non-interactive elements (e.g., ",[191,1015,1016],{},"\u003Ch1>",[191,1018,1019],{},"\u003Cdiv>",") without programmatically adding ",[191,1022,325],{}," prevents focus acceptance.",[188,1025,1026,1029,1030,264,1033,1036],{},[181,1027,1028],{},"Hidden Container Targeting:"," Executing focus on elements inside ",[191,1031,1032],{},"display: none",[191,1034,1035],{},"visibility: hidden"," containers throws DOM exceptions or causes erratic browser behavior.",[188,1038,1039,1042,1043,1045],{},[181,1040,1041],{},"Motion Preference Violations:"," Ignoring ",[191,1044,805],{}," when combining focus restoration with scroll animations triggers vestibular disorders.",[188,1047,1048,1051,1052,1055],{},[181,1049,1050],{},"Scroll-Only Navigation:"," Relying solely on ",[191,1053,1054],{},"window.scrollTo()"," without programmatic focus movement leaves keyboard users stranded at the top of the document with no visual context.",[224,1057,1059],{"id":1058},"frequently-asked-questions","Frequently Asked Questions",[166,1061,1062,1069,1070,1072],{},[181,1063,1064,1065,1068],{},"Q: Why shouldn't I focus the ",[191,1066,1067],{},"\u003Cbody>"," element after a route change?","\nA: Focusing the ",[191,1071,1067],{}," element provides no semantic context to screen readers and breaks expected keyboard navigation patterns. Assistive technologies expect focus to land on a meaningful content landmark or heading that represents the new page state.",[166,1074,1075,1078,1079,1082],{},[181,1076,1077],{},"Q: How do I handle focus restoration when route data loads asynchronously?","\nA: Implement a loading state that delays focus execution until the primary content is fully rendered. Use a ",[191,1080,1081],{},"Promise"," resolution or reactive state flag to trigger the focus routine only after the data fetch and DOM update complete.",[166,1084,1085,1092,1093,1095,1096,1099,1100,1103],{},[181,1086,1087,1088,1091],{},"Q: Does ",[191,1089,1090],{},"preventScroll: true"," improve accessibility during focus restoration?","\nA: No. While ",[191,1094,1090],{}," stops the viewport from jumping, it often hides the focused element off-screen, causing confusion for keyboard users. Use ",[191,1097,1098],{},"preventScroll: false"," to ensure the focused element is visible, or pair it with a controlled ",[191,1101,1102],{},"scrollIntoView()"," call.",[1105,1106,1107],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":377,"searchDepth":405,"depth":405,"links":1109},[1110,1111,1112,1113,1114],{"id":226,"depth":405,"text":227},{"id":318,"depth":405,"text":319},{"id":816,"depth":405,"text":817},{"id":992,"depth":405,"text":993},{"id":1058,"depth":405,"text":1059},null,"Implement dependable focus restoration after SPA route changes so keyboard and screen reader users keep context across dynamic navigation.","md",{},false,{"title":31,"description":1116},"kPR1k8ToRKuzhUgi-8ojjT-BboXjzWSBiouGM1Z1Elg",[1123,1153,1154],{"title":5,"path":6,"stem":7,"children":1124},[1125,1126,1129,1132,1138,1144,1150],{"title":10,"path":6,"stem":11},{"title":13,"path":14,"stem":15,"children":1127},[1128],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":1130},[1131],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":1133},[1134,1135],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":1136},[1137],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":1139},[1140,1141],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":1142},[1143],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":1145},[1146,1147],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":1148},[1149],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":1151},[1152],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69},{"title":71,"path":72,"stem":73,"children":1155},[1156,1157,1163,1169,1172,1181,1190],{"title":76,"path":72,"stem":77},{"title":79,"path":80,"stem":81,"children":1158},[1159,1160],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87,"children":1161},[1162],{"title":85,"path":86,"stem":87},{"title":91,"path":92,"stem":93,"children":1164},[1165,1166],{"title":91,"path":92,"stem":93},{"title":97,"path":98,"stem":99,"children":1167},[1168],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":1170},[1171],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":1173},[1174,1175,1178],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":1176},[1177],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":1179},[1180],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":1182},[1183,1184,1187],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":1185},[1186],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":1188},[1189],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":1191},[1192,1193],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":1194},[1195],{"title":151,"path":152,"stem":153},1778094796116]