[{"data":1,"prerenderedAt":2321},["ShallowReactive",2],{"site-header-nav":3,"page-\u002Ftesting-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002Fasserting-focus-order-in-playwright\u002F":314,"content-navigation":2169},[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":285,"body":316,"date":2162,"description":2163,"extension":2164,"image":2162,"meta":2165,"modifiedAt":2162,"navigation":467,"noindex":2166,"path":286,"publishedAt":2162,"seo":2167,"stem":287,"updatedAt":2162,"__hash__":2168},"content\u002Ftesting-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002Fasserting-focus-order-in-playwright\u002Findex.md",{"type":317,"value":318,"toc":2152},"minimark",[319,323,345,351,369,374,389,392,397,407,412,430,922,925,927,931,939,943,957,1153,1174,1176,1180,1186,1190,1201,1438,1440,1444,1455,1459,1484,1681,1687,1689,1693,1700,1704,1720,1994,1996,2000,2048,2050,2054,2073,2091,2107,2125,2127,2131,2148],[320,321,285],"h1",{"id":322},"asserting-focus-order-in-playwright",[324,325,326,327,331,332,335,336,339,340,344],"p",{},"Focus order is the sequence in which interactive elements receive focus as a user presses ",[328,329,330],"code",{},"Tab",". When that sequence is illogical—or when focus silently drops to ",[328,333,334],{},"\u003Cbody>"," after a dialog closes or a route changes—keyboard and screen-reader users lose their place entirely. This guide shows how to capture the focus sequence in Playwright, assert it against ",[328,337,338],{},"2.4.3 Focus Order",", and verify focus restoration after overlays and client-side navigation. It deepens one slice of ",[341,342,279],"a",{"href":343},"\u002Ftesting-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002F",".",[324,346,347],{},[348,349,350],"strong",{},"WCAG Success Criteria Addressed:",[352,353,354,359,364],"ul",{},[355,356,357],"li",{},[328,358,338],{},[355,360,361],{},[328,362,363],{},"2.1.1 Keyboard",[355,365,366],{},[328,367,368],{},"2.4.7 Focus Visible",[324,370,371],{},[348,372,373],{},"Why Focus Order Tests Matter:",[352,375,376,383,386],{},[355,377,378,379,382],{},"A correct DOM can still produce a broken tab sequence via ",[328,380,381],{},"tabindex"," or portals.",[355,384,385],{},"Focus loss after navigation is invisible to static markup checks.",[355,387,388],{},"Focus restoration is a behavioral contract that only end-to-end tests can prove.",[390,391],"hr",{},[393,394,396],"h2",{"id":395},"capturing-the-focus-sequence","Capturing the Focus Sequence",[324,398,399,400,402,403,406],{},"The most robust way to assert focus order is to capture which element is focused after each ",[328,401,330],{}," press, then compare the captured sequence to the expected one. You read ",[328,404,405],{},"document.activeElement"," inside the page and extract a stable identifier.",[324,408,409],{},[348,410,411],{},"Implementation Guidelines:",[352,413,414,424,427],{},[355,415,416,417,419,420,423],{},"Evaluate ",[328,418,405],{}," in the page context after each press and project it to a comparable value (accessible name, ",[328,421,422],{},"data-testid",", or tag plus text).",[355,425,426],{},"Start from a deterministic anchor so the sequence is reproducible.",[355,428,429],{},"Capture the whole sequence into an array, then assert once—this yields a single, readable diff on failure.",[431,432,437],"pre",{"className":433,"code":434,"language":435,"meta":436,"style":436},"language-ts shiki shiki-themes github-light github-dark","import { test, expect } from '@playwright\u002Ftest';\n\n\u002F\u002F Projects the active element to a stable, human-readable label for comparison.\nasync function activeLabel(page) {\n  return page.evaluate(() => {\n    const el = document.activeElement;\n    if (!el || el === document.body) return 'BODY';\n    return (\n      el.getAttribute('aria-label') ||\n      el.textContent?.trim() ||\n      el.getAttribute('name') ||\n      el.tagName\n    );\n  });\n}\n\ntest('checkout form follows a logical focus order', async ({ page }) => {\n  await page.goto('\u002Fcheckout');\n  await page.evaluate(() => document.body.focus());\n\n  const sequence: string[] = [];\n  for (let i = 0; i \u003C 5; i++) {\n    await page.keyboard.press('Tab');\n    sequence.push(await activeLabel(page));\n  }\n\n  \u002F\u002F A11y rationale: 2.4.3 Focus Order — the tab path must match the visual\n  \u002F\u002F and logical reading order of the form.\n  expect(sequence).toEqual([\n    'Full name',\n    'Email',\n    'Address',\n    'Postal code',\n    'Place order',\n  ]);\n});\n","ts","",[328,438,439,462,469,476,499,520,536,571,580,600,614,630,636,642,648,654,659,687,706,728,733,757,793,812,831,837,842,848,854,869,878,886,894,902,910,916],{"__ignoreMap":436},[440,441,444,448,452,455,459],"span",{"class":442,"line":443},"line",1,[440,445,447],{"class":446},"szBVR","import",[440,449,451],{"class":450},"sVt8B"," { test, expect } ",[440,453,454],{"class":446},"from",[440,456,458],{"class":457},"sZZnC"," '@playwright\u002Ftest'",[440,460,461],{"class":450},";\n",[440,463,465],{"class":442,"line":464},2,[440,466,468],{"emptyLinePlaceholder":467},true,"\n",[440,470,472],{"class":442,"line":471},3,[440,473,475],{"class":474},"sJ8bj","\u002F\u002F Projects the active element to a stable, human-readable label for comparison.\n",[440,477,479,482,485,489,492,496],{"class":442,"line":478},4,[440,480,481],{"class":446},"async",[440,483,484],{"class":446}," function",[440,486,488],{"class":487},"sScJk"," activeLabel",[440,490,491],{"class":450},"(",[440,493,495],{"class":494},"s4XuR","page",[440,497,498],{"class":450},") {\n",[440,500,502,505,508,511,514,517],{"class":442,"line":501},5,[440,503,504],{"class":446},"  return",[440,506,507],{"class":450}," page.",[440,509,510],{"class":487},"evaluate",[440,512,513],{"class":450},"(() ",[440,515,516],{"class":446},"=>",[440,518,519],{"class":450}," {\n",[440,521,523,526,530,533],{"class":442,"line":522},6,[440,524,525],{"class":446},"    const",[440,527,529],{"class":528},"sj4cs"," el",[440,531,532],{"class":446}," =",[440,534,535],{"class":450}," document.activeElement;\n",[440,537,539,542,545,548,551,554,557,560,563,566,569],{"class":442,"line":538},7,[440,540,541],{"class":446},"    if",[440,543,544],{"class":450}," (",[440,546,547],{"class":446},"!",[440,549,550],{"class":450},"el ",[440,552,553],{"class":446},"||",[440,555,556],{"class":450}," el ",[440,558,559],{"class":446},"===",[440,561,562],{"class":450}," document.body) ",[440,564,565],{"class":446},"return",[440,567,568],{"class":457}," 'BODY'",[440,570,461],{"class":450},[440,572,574,577],{"class":442,"line":573},8,[440,575,576],{"class":446},"    return",[440,578,579],{"class":450}," (\n",[440,581,583,586,589,591,594,597],{"class":442,"line":582},9,[440,584,585],{"class":450},"      el.",[440,587,588],{"class":487},"getAttribute",[440,590,491],{"class":450},[440,592,593],{"class":457},"'aria-label'",[440,595,596],{"class":450},") ",[440,598,599],{"class":446},"||\n",[440,601,603,606,609,612],{"class":442,"line":602},10,[440,604,605],{"class":450},"      el.textContent?.",[440,607,608],{"class":487},"trim",[440,610,611],{"class":450},"() ",[440,613,599],{"class":446},[440,615,617,619,621,623,626,628],{"class":442,"line":616},11,[440,618,585],{"class":450},[440,620,588],{"class":487},[440,622,491],{"class":450},[440,624,625],{"class":457},"'name'",[440,627,596],{"class":450},[440,629,599],{"class":446},[440,631,633],{"class":442,"line":632},12,[440,634,635],{"class":450},"      el.tagName\n",[440,637,639],{"class":442,"line":638},13,[440,640,641],{"class":450},"    );\n",[440,643,645],{"class":442,"line":644},14,[440,646,647],{"class":450},"  });\n",[440,649,651],{"class":442,"line":650},15,[440,652,653],{"class":450},"}\n",[440,655,657],{"class":442,"line":656},16,[440,658,468],{"emptyLinePlaceholder":467},[440,660,662,665,667,670,673,675,678,680,683,685],{"class":442,"line":661},17,[440,663,664],{"class":487},"test",[440,666,491],{"class":450},[440,668,669],{"class":457},"'checkout form follows a logical focus order'",[440,671,672],{"class":450},", ",[440,674,481],{"class":446},[440,676,677],{"class":450}," ({ ",[440,679,495],{"class":494},[440,681,682],{"class":450}," }) ",[440,684,516],{"class":446},[440,686,519],{"class":450},[440,688,690,693,695,698,700,703],{"class":442,"line":689},18,[440,691,692],{"class":446},"  await",[440,694,507],{"class":450},[440,696,697],{"class":487},"goto",[440,699,491],{"class":450},[440,701,702],{"class":457},"'\u002Fcheckout'",[440,704,705],{"class":450},");\n",[440,707,709,711,713,715,717,719,722,725],{"class":442,"line":708},19,[440,710,692],{"class":446},[440,712,507],{"class":450},[440,714,510],{"class":487},[440,716,513],{"class":450},[440,718,516],{"class":446},[440,720,721],{"class":450}," document.body.",[440,723,724],{"class":487},"focus",[440,726,727],{"class":450},"());\n",[440,729,731],{"class":442,"line":730},20,[440,732,468],{"emptyLinePlaceholder":467},[440,734,736,739,742,745,748,751,754],{"class":442,"line":735},21,[440,737,738],{"class":446},"  const",[440,740,741],{"class":528}," sequence",[440,743,744],{"class":446},":",[440,746,747],{"class":528}," string",[440,749,750],{"class":450},"[] ",[440,752,753],{"class":446},"=",[440,755,756],{"class":450}," [];\n",[440,758,760,763,765,768,771,773,776,779,782,785,788,791],{"class":442,"line":759},22,[440,761,762],{"class":446},"  for",[440,764,544],{"class":450},[440,766,767],{"class":446},"let",[440,769,770],{"class":450}," i ",[440,772,753],{"class":446},[440,774,775],{"class":528}," 0",[440,777,778],{"class":450},"; i ",[440,780,781],{"class":446},"\u003C",[440,783,784],{"class":528}," 5",[440,786,787],{"class":450},"; i",[440,789,790],{"class":446},"++",[440,792,498],{"class":450},[440,794,796,799,802,805,807,810],{"class":442,"line":795},23,[440,797,798],{"class":446},"    await",[440,800,801],{"class":450}," page.keyboard.",[440,803,804],{"class":487},"press",[440,806,491],{"class":450},[440,808,809],{"class":457},"'Tab'",[440,811,705],{"class":450},[440,813,815,818,821,823,826,828],{"class":442,"line":814},24,[440,816,817],{"class":450},"    sequence.",[440,819,820],{"class":487},"push",[440,822,491],{"class":450},[440,824,825],{"class":446},"await",[440,827,488],{"class":487},[440,829,830],{"class":450},"(page));\n",[440,832,834],{"class":442,"line":833},25,[440,835,836],{"class":450},"  }\n",[440,838,840],{"class":442,"line":839},26,[440,841,468],{"emptyLinePlaceholder":467},[440,843,845],{"class":442,"line":844},27,[440,846,847],{"class":474},"  \u002F\u002F A11y rationale: 2.4.3 Focus Order — the tab path must match the visual\n",[440,849,851],{"class":442,"line":850},28,[440,852,853],{"class":474},"  \u002F\u002F and logical reading order of the form.\n",[440,855,857,860,863,866],{"class":442,"line":856},29,[440,858,859],{"class":487},"  expect",[440,861,862],{"class":450},"(sequence).",[440,864,865],{"class":487},"toEqual",[440,867,868],{"class":450},"([\n",[440,870,872,875],{"class":442,"line":871},30,[440,873,874],{"class":457},"    'Full name'",[440,876,877],{"class":450},",\n",[440,879,881,884],{"class":442,"line":880},31,[440,882,883],{"class":457},"    'Email'",[440,885,877],{"class":450},[440,887,889,892],{"class":442,"line":888},32,[440,890,891],{"class":457},"    'Address'",[440,893,877],{"class":450},[440,895,897,900],{"class":442,"line":896},33,[440,898,899],{"class":457},"    'Postal code'",[440,901,877],{"class":450},[440,903,905,908],{"class":442,"line":904},34,[440,906,907],{"class":457},"    'Place order'",[440,909,877],{"class":450},[440,911,913],{"class":442,"line":912},35,[440,914,915],{"class":450},"  ]);\n",[440,917,919],{"class":442,"line":918},36,[440,920,921],{"class":450},"});\n",[324,923,924],{},"Capturing the full array rather than asserting element-by-element gives you a clear diff when the order regresses, pinpointing exactly where the sequence diverged.",[390,926],{},[393,928,930],{"id":929},"asserting-logical-order-against-243","Asserting Logical Order Against 2.4.3",[324,932,933,935,936,938],{},[328,934,338],{}," requires that the focus sequence preserves meaning and operability—it should generally follow the visual reading order. The most common violations are positive ",[328,937,381],{}," values that yank elements to the front of the sequence, and portal-rendered content (modals, menus) that appears at the end of the DOM but should be reached in context.",[324,940,941],{},[348,942,411],{},[352,944,945,951,954],{},[355,946,947,948,950],{},"Assert that no element carries a positive ",[328,949,381],{},", which is almost always a focus-order smell.",[355,952,953],{},"Confirm visually adjacent controls are also adjacent in the tab sequence.",[355,955,956],{},"For portaled overlays, assert focus moves into the overlay when it opens rather than continuing past it.",[431,958,960],{"className":433,"code":959,"language":435,"meta":436,"style":436},"test('no positive tabindex distorts the focus order', async ({ page }) => {\n  await page.goto('\u002Fcheckout');\n\n  \u002F\u002F A11y rationale: 2.4.3 Focus Order — positive tabindex overrides DOM order\n  \u002F\u002F and almost always breaks the logical sequence.\n  const positiveTabindex = await page.evaluate(() =>\n    Array.from(document.querySelectorAll('[tabindex]'))\n      .filter((el) => Number(el.getAttribute('tabindex')) > 0)\n      .map((el) => el.getAttribute('aria-label') || el.tagName),\n  );\n\n  expect(positiveTabindex).toEqual([]);\n});\n",[328,961,962,985,999,1003,1008,1013,1034,1055,1097,1128,1133,1137,1149],{"__ignoreMap":436},[440,963,964,966,968,971,973,975,977,979,981,983],{"class":442,"line":443},[440,965,664],{"class":487},[440,967,491],{"class":450},[440,969,970],{"class":457},"'no positive tabindex distorts the focus order'",[440,972,672],{"class":450},[440,974,481],{"class":446},[440,976,677],{"class":450},[440,978,495],{"class":494},[440,980,682],{"class":450},[440,982,516],{"class":446},[440,984,519],{"class":450},[440,986,987,989,991,993,995,997],{"class":442,"line":464},[440,988,692],{"class":446},[440,990,507],{"class":450},[440,992,697],{"class":487},[440,994,491],{"class":450},[440,996,702],{"class":457},[440,998,705],{"class":450},[440,1000,1001],{"class":442,"line":471},[440,1002,468],{"emptyLinePlaceholder":467},[440,1004,1005],{"class":442,"line":478},[440,1006,1007],{"class":474},"  \u002F\u002F A11y rationale: 2.4.3 Focus Order — positive tabindex overrides DOM order\n",[440,1009,1010],{"class":442,"line":501},[440,1011,1012],{"class":474},"  \u002F\u002F and almost always breaks the logical sequence.\n",[440,1014,1015,1017,1020,1022,1025,1027,1029,1031],{"class":442,"line":522},[440,1016,738],{"class":446},[440,1018,1019],{"class":528}," positiveTabindex",[440,1021,532],{"class":446},[440,1023,1024],{"class":446}," await",[440,1026,507],{"class":450},[440,1028,510],{"class":487},[440,1030,513],{"class":450},[440,1032,1033],{"class":446},"=>\n",[440,1035,1036,1039,1041,1044,1047,1049,1052],{"class":442,"line":538},[440,1037,1038],{"class":450},"    Array.",[440,1040,454],{"class":487},[440,1042,1043],{"class":450},"(document.",[440,1045,1046],{"class":487},"querySelectorAll",[440,1048,491],{"class":450},[440,1050,1051],{"class":457},"'[tabindex]'",[440,1053,1054],{"class":450},"))\n",[440,1056,1057,1060,1063,1066,1069,1071,1073,1076,1079,1081,1083,1086,1089,1092,1094],{"class":442,"line":573},[440,1058,1059],{"class":450},"      .",[440,1061,1062],{"class":487},"filter",[440,1064,1065],{"class":450},"((",[440,1067,1068],{"class":494},"el",[440,1070,596],{"class":450},[440,1072,516],{"class":446},[440,1074,1075],{"class":487}," Number",[440,1077,1078],{"class":450},"(el.",[440,1080,588],{"class":487},[440,1082,491],{"class":450},[440,1084,1085],{"class":457},"'tabindex'",[440,1087,1088],{"class":450},")) ",[440,1090,1091],{"class":446},">",[440,1093,775],{"class":528},[440,1095,1096],{"class":450},")\n",[440,1098,1099,1101,1104,1106,1108,1110,1112,1115,1117,1119,1121,1123,1125],{"class":442,"line":582},[440,1100,1059],{"class":450},[440,1102,1103],{"class":487},"map",[440,1105,1065],{"class":450},[440,1107,1068],{"class":494},[440,1109,596],{"class":450},[440,1111,516],{"class":446},[440,1113,1114],{"class":450}," el.",[440,1116,588],{"class":487},[440,1118,491],{"class":450},[440,1120,593],{"class":457},[440,1122,596],{"class":450},[440,1124,553],{"class":446},[440,1126,1127],{"class":450}," el.tagName),\n",[440,1129,1130],{"class":442,"line":602},[440,1131,1132],{"class":450},"  );\n",[440,1134,1135],{"class":442,"line":616},[440,1136,468],{"emptyLinePlaceholder":467},[440,1138,1139,1141,1144,1146],{"class":442,"line":632},[440,1140,859],{"class":487},[440,1142,1143],{"class":450},"(positiveTabindex).",[440,1145,865],{"class":487},[440,1147,1148],{"class":450},"([]);\n",[440,1150,1151],{"class":442,"line":638},[440,1152,921],{"class":450},[324,1154,1155,1156,1158,1159,86,1162,1165,1166,1169,1170,344],{},"For the keyboard-driven flows that feed these order assertions—pressing ",[328,1157,330],{},", activating with ",[328,1160,1161],{},"Enter",[328,1163,1164],{},"Space",", and closing with ",[328,1167,1168],{},"Escape","—see ",[341,1171,1173],{"href":1172},"\u002Ftesting-and-automating-accessibility\u002Fend-to-end-accessibility-testing-with-playwright\u002Fkeyboard-navigation-tests-in-playwright\u002F","keyboard navigation tests in Playwright",[390,1175],{},[393,1177,1179],{"id":1178},"verifying-focus-restoration-after-a-dialog","Verifying Focus Restoration After a Dialog",[324,1181,1182,1183,1185],{},"When a dialog closes, focus must return to the control that opened it. Otherwise the user is dropped at the top of the document—or onto ",[328,1184,334],{},"—and must re-traverse the entire page to find their place.",[324,1187,1188],{},[348,1189,411],{},[352,1191,1192,1195,1198],{},[355,1193,1194],{},"Record the trigger before opening, open the dialog, and assert focus entered it.",[355,1196,1197],{},"Close the dialog and assert focus is back on the exact trigger element.",[355,1199,1200],{},"Test closing by every available path (Escape, the close button, the confirm button) since each can restore focus differently.",[431,1202,1204],{"className":433,"code":1203,"language":435,"meta":436,"style":436},"test('focus returns to the trigger after the dialog closes', async ({ page }) => {\n  await page.goto('\u002Faccount');\n\n  const trigger = page.getByRole('button', { name: 'Edit profile' });\n  await trigger.focus();\n  await page.keyboard.press('Enter');\n\n  const dialog = page.getByRole('dialog', { name: 'Edit profile' });\n  await expect(dialog).toBeVisible();\n  await expect(dialog.getByRole('textbox', { name: 'Display name' })).toBeFocused();\n\n  await page.keyboard.press('Escape');\n  await expect(dialog).toBeHidden();\n\n  \u002F\u002F A11y rationale: 2.4.3 Focus Order — restoring focus to the trigger keeps\n  \u002F\u002F the user's place instead of dumping them at the top of the document.\n  await expect(trigger).toBeFocused();\n});\n",[328,1205,1206,1229,1244,1248,1276,1288,1303,1307,1331,1346,1375,1379,1394,1407,1411,1416,1421,1434],{"__ignoreMap":436},[440,1207,1208,1210,1212,1215,1217,1219,1221,1223,1225,1227],{"class":442,"line":443},[440,1209,664],{"class":487},[440,1211,491],{"class":450},[440,1213,1214],{"class":457},"'focus returns to the trigger after the dialog closes'",[440,1216,672],{"class":450},[440,1218,481],{"class":446},[440,1220,677],{"class":450},[440,1222,495],{"class":494},[440,1224,682],{"class":450},[440,1226,516],{"class":446},[440,1228,519],{"class":450},[440,1230,1231,1233,1235,1237,1239,1242],{"class":442,"line":464},[440,1232,692],{"class":446},[440,1234,507],{"class":450},[440,1236,697],{"class":487},[440,1238,491],{"class":450},[440,1240,1241],{"class":457},"'\u002Faccount'",[440,1243,705],{"class":450},[440,1245,1246],{"class":442,"line":471},[440,1247,468],{"emptyLinePlaceholder":467},[440,1249,1250,1252,1255,1257,1259,1262,1264,1267,1270,1273],{"class":442,"line":478},[440,1251,738],{"class":446},[440,1253,1254],{"class":528}," trigger",[440,1256,532],{"class":446},[440,1258,507],{"class":450},[440,1260,1261],{"class":487},"getByRole",[440,1263,491],{"class":450},[440,1265,1266],{"class":457},"'button'",[440,1268,1269],{"class":450},", { name: ",[440,1271,1272],{"class":457},"'Edit profile'",[440,1274,1275],{"class":450}," });\n",[440,1277,1278,1280,1283,1285],{"class":442,"line":501},[440,1279,692],{"class":446},[440,1281,1282],{"class":450}," trigger.",[440,1284,724],{"class":487},[440,1286,1287],{"class":450},"();\n",[440,1289,1290,1292,1294,1296,1298,1301],{"class":442,"line":522},[440,1291,692],{"class":446},[440,1293,801],{"class":450},[440,1295,804],{"class":487},[440,1297,491],{"class":450},[440,1299,1300],{"class":457},"'Enter'",[440,1302,705],{"class":450},[440,1304,1305],{"class":442,"line":538},[440,1306,468],{"emptyLinePlaceholder":467},[440,1308,1309,1311,1314,1316,1318,1320,1322,1325,1327,1329],{"class":442,"line":573},[440,1310,738],{"class":446},[440,1312,1313],{"class":528}," dialog",[440,1315,532],{"class":446},[440,1317,507],{"class":450},[440,1319,1261],{"class":487},[440,1321,491],{"class":450},[440,1323,1324],{"class":457},"'dialog'",[440,1326,1269],{"class":450},[440,1328,1272],{"class":457},[440,1330,1275],{"class":450},[440,1332,1333,1335,1338,1341,1344],{"class":442,"line":582},[440,1334,692],{"class":446},[440,1336,1337],{"class":487}," expect",[440,1339,1340],{"class":450},"(dialog).",[440,1342,1343],{"class":487},"toBeVisible",[440,1345,1287],{"class":450},[440,1347,1348,1350,1352,1355,1357,1359,1362,1364,1367,1370,1373],{"class":442,"line":602},[440,1349,692],{"class":446},[440,1351,1337],{"class":487},[440,1353,1354],{"class":450},"(dialog.",[440,1356,1261],{"class":487},[440,1358,491],{"class":450},[440,1360,1361],{"class":457},"'textbox'",[440,1363,1269],{"class":450},[440,1365,1366],{"class":457},"'Display name'",[440,1368,1369],{"class":450}," })).",[440,1371,1372],{"class":487},"toBeFocused",[440,1374,1287],{"class":450},[440,1376,1377],{"class":442,"line":616},[440,1378,468],{"emptyLinePlaceholder":467},[440,1380,1381,1383,1385,1387,1389,1392],{"class":442,"line":632},[440,1382,692],{"class":446},[440,1384,801],{"class":450},[440,1386,804],{"class":487},[440,1388,491],{"class":450},[440,1390,1391],{"class":457},"'Escape'",[440,1393,705],{"class":450},[440,1395,1396,1398,1400,1402,1405],{"class":442,"line":638},[440,1397,692],{"class":446},[440,1399,1337],{"class":487},[440,1401,1340],{"class":450},[440,1403,1404],{"class":487},"toBeHidden",[440,1406,1287],{"class":450},[440,1408,1409],{"class":442,"line":644},[440,1410,468],{"emptyLinePlaceholder":467},[440,1412,1413],{"class":442,"line":650},[440,1414,1415],{"class":474},"  \u002F\u002F A11y rationale: 2.4.3 Focus Order — restoring focus to the trigger keeps\n",[440,1417,1418],{"class":442,"line":656},[440,1419,1420],{"class":474},"  \u002F\u002F the user's place instead of dumping them at the top of the document.\n",[440,1422,1423,1425,1427,1430,1432],{"class":442,"line":661},[440,1424,692],{"class":446},[440,1426,1337],{"class":487},[440,1428,1429],{"class":450},"(trigger).",[440,1431,1372],{"class":487},[440,1433,1287],{"class":450},[440,1435,1436],{"class":442,"line":689},[440,1437,921],{"class":450},[390,1439],{},[393,1441,1443],{"id":1442},"verifying-focus-after-client-side-navigation","Verifying Focus After Client-Side Navigation",[324,1445,1446,1447,1451,1452,1454],{},"A single-page-app route change swaps the view without a browser load, so focus is ",[1448,1449,1450],"em",{},"not"," reset by the platform. Without explicit handling, focus stays on a link that may no longer exist or collapses to ",[328,1453,334],{},". The framework must deliberately move focus to the new page's heading or main landmark.",[324,1456,1457],{},[348,1458,411],{},[352,1460,1461,1471,1478],{},[355,1462,1463,1464,1466,1467,1470],{},"After navigating by keyboard, assert focus lands on the new ",[328,1465,320],{}," or ",[328,1468,1469],{},"main"," region.",[355,1472,1473,1474,1477],{},"Confirm the target is programmatically focusable (",[328,1475,1476],{},"tabindex=\"-1\""," on a heading or landmark).",[355,1479,1480,1481,1483],{},"Re-run a couple of ",[328,1482,330],{}," presses afterward to confirm the sequence continues from the new focus point, not from the top.",[431,1485,1487],{"className":433,"code":1486,"language":435,"meta":436,"style":436},"test('SPA navigation moves focus to the new page heading', async ({ page }) => {\n  await page.goto('\u002F');\n\n  await page.getByRole('link', { name: 'Pricing' }).focus();\n  await page.keyboard.press('Enter');\n\n  \u002F\u002F A11y rationale: 2.4.3 Focus Order — after a client-side route change,\n  \u002F\u002F focus must move to a logical landmark in the new view.\n  const heading = page.getByRole('heading', { level: 1, name: 'Pricing' });\n  await expect(heading).toBeFocused();\n\n  \u002F\u002F The next Tab should continue inside the new page, not restart at the top.\n  await page.keyboard.press('Tab');\n  await expect(page.getByRole('link', { name: 'Compare plans' })).toBeFocused();\n});\n",[328,1488,1489,1512,1527,1531,1556,1570,1574,1579,1584,1615,1628,1632,1637,1651,1677],{"__ignoreMap":436},[440,1490,1491,1493,1495,1498,1500,1502,1504,1506,1508,1510],{"class":442,"line":443},[440,1492,664],{"class":487},[440,1494,491],{"class":450},[440,1496,1497],{"class":457},"'SPA navigation moves focus to the new page heading'",[440,1499,672],{"class":450},[440,1501,481],{"class":446},[440,1503,677],{"class":450},[440,1505,495],{"class":494},[440,1507,682],{"class":450},[440,1509,516],{"class":446},[440,1511,519],{"class":450},[440,1513,1514,1516,1518,1520,1522,1525],{"class":442,"line":464},[440,1515,692],{"class":446},[440,1517,507],{"class":450},[440,1519,697],{"class":487},[440,1521,491],{"class":450},[440,1523,1524],{"class":457},"'\u002F'",[440,1526,705],{"class":450},[440,1528,1529],{"class":442,"line":471},[440,1530,468],{"emptyLinePlaceholder":467},[440,1532,1533,1535,1537,1539,1541,1544,1546,1549,1552,1554],{"class":442,"line":478},[440,1534,692],{"class":446},[440,1536,507],{"class":450},[440,1538,1261],{"class":487},[440,1540,491],{"class":450},[440,1542,1543],{"class":457},"'link'",[440,1545,1269],{"class":450},[440,1547,1548],{"class":457},"'Pricing'",[440,1550,1551],{"class":450}," }).",[440,1553,724],{"class":487},[440,1555,1287],{"class":450},[440,1557,1558,1560,1562,1564,1566,1568],{"class":442,"line":501},[440,1559,692],{"class":446},[440,1561,801],{"class":450},[440,1563,804],{"class":487},[440,1565,491],{"class":450},[440,1567,1300],{"class":457},[440,1569,705],{"class":450},[440,1571,1572],{"class":442,"line":522},[440,1573,468],{"emptyLinePlaceholder":467},[440,1575,1576],{"class":442,"line":538},[440,1577,1578],{"class":474},"  \u002F\u002F A11y rationale: 2.4.3 Focus Order — after a client-side route change,\n",[440,1580,1581],{"class":442,"line":573},[440,1582,1583],{"class":474},"  \u002F\u002F focus must move to a logical landmark in the new view.\n",[440,1585,1586,1588,1591,1593,1595,1597,1599,1602,1605,1608,1611,1613],{"class":442,"line":582},[440,1587,738],{"class":446},[440,1589,1590],{"class":528}," heading",[440,1592,532],{"class":446},[440,1594,507],{"class":450},[440,1596,1261],{"class":487},[440,1598,491],{"class":450},[440,1600,1601],{"class":457},"'heading'",[440,1603,1604],{"class":450},", { level: ",[440,1606,1607],{"class":528},"1",[440,1609,1610],{"class":450},", name: ",[440,1612,1548],{"class":457},[440,1614,1275],{"class":450},[440,1616,1617,1619,1621,1624,1626],{"class":442,"line":602},[440,1618,692],{"class":446},[440,1620,1337],{"class":487},[440,1622,1623],{"class":450},"(heading).",[440,1625,1372],{"class":487},[440,1627,1287],{"class":450},[440,1629,1630],{"class":442,"line":616},[440,1631,468],{"emptyLinePlaceholder":467},[440,1633,1634],{"class":442,"line":632},[440,1635,1636],{"class":474},"  \u002F\u002F The next Tab should continue inside the new page, not restart at the top.\n",[440,1638,1639,1641,1643,1645,1647,1649],{"class":442,"line":638},[440,1640,692],{"class":446},[440,1642,801],{"class":450},[440,1644,804],{"class":487},[440,1646,491],{"class":450},[440,1648,809],{"class":457},[440,1650,705],{"class":450},[440,1652,1653,1655,1657,1660,1662,1664,1666,1668,1671,1673,1675],{"class":442,"line":644},[440,1654,692],{"class":446},[440,1656,1337],{"class":487},[440,1658,1659],{"class":450},"(page.",[440,1661,1261],{"class":487},[440,1663,491],{"class":450},[440,1665,1543],{"class":457},[440,1667,1269],{"class":450},[440,1669,1670],{"class":457},"'Compare plans'",[440,1672,1369],{"class":450},[440,1674,1372],{"class":487},[440,1676,1287],{"class":450},[440,1678,1679],{"class":442,"line":650},[440,1680,921],{"class":450},[324,1682,1683,1684,344],{},"The framework-side patterns these assertions exercise are covered in ",[341,1685,25],{"href":1686},"\u002Fcore-accessibility-principles-for-modern-frameworks\u002Ffocus-management-strategies-for-spas\u002F",[390,1688],{},[393,1690,1692],{"id":1691},"detecting-focus-loss-to-the-body","Detecting Focus Loss to the Body",[324,1694,1695,1696,1699],{},"The most damaging focus bug is silent: focus falls back to ",[328,1697,1698],{},"document.body",", leaving the user with no visible indicator and no obvious next stop. Because nothing throws, this slips through unless you assert against it explicitly.",[324,1701,1702],{},[348,1703,411],{},[352,1705,1706,1714,1717],{},[355,1707,1708,1709,1711,1712,344],{},"After any focus-moving action, assert that ",[328,1710,405],{}," is not ",[328,1713,1698],{},[355,1715,1716],{},"Build a small helper and apply it liberally after navigations and overlay closes.",[355,1718,1719],{},"Treat a focused body as a hard failure, not a warning.",[431,1721,1723],{"className":433,"code":1722,"language":435,"meta":436,"style":436},"\u002F\u002F Fails loudly when focus has collapsed to the document body.\nasync function expectFocusNotOnBody(page) {\n  const onBody = await page.evaluate(\n    () => document.activeElement === document.body || document.activeElement === null,\n  );\n  expect(onBody, 'focus must not fall back to \u003Cbody>').toBe(false);\n}\n\ntest('closing the menu never drops focus to the body', async ({ page }) => {\n  await page.goto('\u002F');\n\n  const menuButton = page.getByRole('button', { name: 'Account menu' });\n  await menuButton.focus();\n  await page.keyboard.press('Enter');\n  await expect(page.getByRole('menu')).toBeVisible();\n\n  await page.keyboard.press('Escape');\n\n  \u002F\u002F A11y rationale: 2.1.1 Keyboard \u002F 2.4.3 Focus Order — focus must land on a\n  \u002F\u002F real control, never on the body where the user loses their place.\n  await expectFocusNotOnBody(page);\n  await expect(menuButton).toBeFocused();\n});\n",[328,1724,1725,1730,1745,1763,1789,1793,1816,1820,1824,1847,1861,1865,1889,1900,1914,1936,1940,1954,1958,1963,1968,1977,1990],{"__ignoreMap":436},[440,1726,1727],{"class":442,"line":443},[440,1728,1729],{"class":474},"\u002F\u002F Fails loudly when focus has collapsed to the document body.\n",[440,1731,1732,1734,1736,1739,1741,1743],{"class":442,"line":464},[440,1733,481],{"class":446},[440,1735,484],{"class":446},[440,1737,1738],{"class":487}," expectFocusNotOnBody",[440,1740,491],{"class":450},[440,1742,495],{"class":494},[440,1744,498],{"class":450},[440,1746,1747,1749,1752,1754,1756,1758,1760],{"class":442,"line":471},[440,1748,738],{"class":446},[440,1750,1751],{"class":528}," onBody",[440,1753,532],{"class":446},[440,1755,1024],{"class":446},[440,1757,507],{"class":450},[440,1759,510],{"class":487},[440,1761,1762],{"class":450},"(\n",[440,1764,1765,1768,1770,1773,1775,1778,1780,1782,1784,1787],{"class":442,"line":478},[440,1766,1767],{"class":450},"    () ",[440,1769,516],{"class":446},[440,1771,1772],{"class":450}," document.activeElement ",[440,1774,559],{"class":446},[440,1776,1777],{"class":450}," document.body ",[440,1779,553],{"class":446},[440,1781,1772],{"class":450},[440,1783,559],{"class":446},[440,1785,1786],{"class":528}," null",[440,1788,877],{"class":450},[440,1790,1791],{"class":442,"line":501},[440,1792,1132],{"class":450},[440,1794,1795,1797,1800,1803,1806,1809,1811,1814],{"class":442,"line":522},[440,1796,859],{"class":487},[440,1798,1799],{"class":450},"(onBody, ",[440,1801,1802],{"class":457},"'focus must not fall back to \u003Cbody>'",[440,1804,1805],{"class":450},").",[440,1807,1808],{"class":487},"toBe",[440,1810,491],{"class":450},[440,1812,1813],{"class":528},"false",[440,1815,705],{"class":450},[440,1817,1818],{"class":442,"line":538},[440,1819,653],{"class":450},[440,1821,1822],{"class":442,"line":573},[440,1823,468],{"emptyLinePlaceholder":467},[440,1825,1826,1828,1830,1833,1835,1837,1839,1841,1843,1845],{"class":442,"line":582},[440,1827,664],{"class":487},[440,1829,491],{"class":450},[440,1831,1832],{"class":457},"'closing the menu never drops focus to the body'",[440,1834,672],{"class":450},[440,1836,481],{"class":446},[440,1838,677],{"class":450},[440,1840,495],{"class":494},[440,1842,682],{"class":450},[440,1844,516],{"class":446},[440,1846,519],{"class":450},[440,1848,1849,1851,1853,1855,1857,1859],{"class":442,"line":602},[440,1850,692],{"class":446},[440,1852,507],{"class":450},[440,1854,697],{"class":487},[440,1856,491],{"class":450},[440,1858,1524],{"class":457},[440,1860,705],{"class":450},[440,1862,1863],{"class":442,"line":616},[440,1864,468],{"emptyLinePlaceholder":467},[440,1866,1867,1869,1872,1874,1876,1878,1880,1882,1884,1887],{"class":442,"line":632},[440,1868,738],{"class":446},[440,1870,1871],{"class":528}," menuButton",[440,1873,532],{"class":446},[440,1875,507],{"class":450},[440,1877,1261],{"class":487},[440,1879,491],{"class":450},[440,1881,1266],{"class":457},[440,1883,1269],{"class":450},[440,1885,1886],{"class":457},"'Account menu'",[440,1888,1275],{"class":450},[440,1890,1891,1893,1896,1898],{"class":442,"line":638},[440,1892,692],{"class":446},[440,1894,1895],{"class":450}," menuButton.",[440,1897,724],{"class":487},[440,1899,1287],{"class":450},[440,1901,1902,1904,1906,1908,1910,1912],{"class":442,"line":644},[440,1903,692],{"class":446},[440,1905,801],{"class":450},[440,1907,804],{"class":487},[440,1909,491],{"class":450},[440,1911,1300],{"class":457},[440,1913,705],{"class":450},[440,1915,1916,1918,1920,1922,1924,1926,1929,1932,1934],{"class":442,"line":650},[440,1917,692],{"class":446},[440,1919,1337],{"class":487},[440,1921,1659],{"class":450},[440,1923,1261],{"class":487},[440,1925,491],{"class":450},[440,1927,1928],{"class":457},"'menu'",[440,1930,1931],{"class":450},")).",[440,1933,1343],{"class":487},[440,1935,1287],{"class":450},[440,1937,1938],{"class":442,"line":656},[440,1939,468],{"emptyLinePlaceholder":467},[440,1941,1942,1944,1946,1948,1950,1952],{"class":442,"line":661},[440,1943,692],{"class":446},[440,1945,801],{"class":450},[440,1947,804],{"class":487},[440,1949,491],{"class":450},[440,1951,1391],{"class":457},[440,1953,705],{"class":450},[440,1955,1956],{"class":442,"line":689},[440,1957,468],{"emptyLinePlaceholder":467},[440,1959,1960],{"class":442,"line":708},[440,1961,1962],{"class":474},"  \u002F\u002F A11y rationale: 2.1.1 Keyboard \u002F 2.4.3 Focus Order — focus must land on a\n",[440,1964,1965],{"class":442,"line":730},[440,1966,1967],{"class":474},"  \u002F\u002F real control, never on the body where the user loses their place.\n",[440,1969,1970,1972,1974],{"class":442,"line":735},[440,1971,692],{"class":446},[440,1973,1738],{"class":487},[440,1975,1976],{"class":450},"(page);\n",[440,1978,1979,1981,1983,1986,1988],{"class":442,"line":759},[440,1980,692],{"class":446},[440,1982,1337],{"class":487},[440,1984,1985],{"class":450},"(menuButton).",[440,1987,1372],{"class":487},[440,1989,1287],{"class":450},[440,1991,1992],{"class":442,"line":795},[440,1993,921],{"class":450},[390,1995],{},[393,1997,1999],{"id":1998},"how-to-verify","How to Verify",[352,2001,2002,2010,2016,2029,2038],{},[355,2003,2004,2007,2008,344],{},[348,2005,2006],{},"Manual traversal."," Tab through each tested flow by hand and confirm the order, restoration, and navigation focus match your captured sequences. Your eyes are the ground truth for ",[328,2009,338],{},[355,2011,2012,2015],{},[348,2013,2014],{},"Screen-reader pass."," With NVDA or VoiceOver running, confirm the new page heading is announced after navigation and the dialog name is announced on open—proof that focus moved somewhere meaningful.",[355,2017,2018,2021,2022,2024,2025,2028],{},[348,2019,2020],{},"Visible-ring check."," Confirm a visible focus indicator at every captured stop to satisfy ",[328,2023,368],{},"; ",[328,2026,2027],{},"toBeFocused()"," cannot see the ring.",[355,2030,2031,2034,2035,2037],{},[348,2032,2033],{},"Regression failure test."," Comment out the framework's focus-on-navigation or focus-restoration logic and confirm the relevant test fails by detecting focus on ",[328,2036,334],{},". A test that cannot catch the regression is not protecting you.",[355,2039,2040,2043,2044,2047],{},[348,2041,2042],{},"Axe cross-check."," Run ",[328,2045,2046],{},"@axe-core\u002Fplaywright"," to flag missing accessible names that would make your captured labels ambiguous or unstable.",[390,2049],{},[393,2051,2053],{"id":2052},"frequently-asked-questions","Frequently Asked Questions",[324,2055,2056,2059,2060,2062,2063,2066,2067,2069,2070,2072],{},[348,2057,2058],{},"How do I read which element is currently focused in Playwright?","\nEvaluate ",[328,2061,405],{}," inside the page with ",[328,2064,2065],{},"page.evaluate()"," and project it to a stable label—its accessible name, ",[328,2068,422],{},", or text. Capturing that after each ",[328,2071,330],{}," press builds a comparable sequence you can assert against in one expectation.",[324,2074,2075,2081,2082,2084,2085,2087,2088,2090],{},[348,2076,2077,2078,2080],{},"Why assert against a positive ",[328,2079,381],{},"?","\nAny ",[328,2083,381],{}," greater than zero pulls an element to the front of the global tab order, overriding DOM order and almost always violating ",[328,2086,338],{},". Asserting that no element carries a positive ",[328,2089,381],{}," catches an entire class of order regressions cheaply.",[324,2092,2093,2096,2097,2099,2100,2102,2103,2106],{},[348,2094,2095],{},"How do I prove focus didn't silently fall to the body?","\nAdd an explicit check that ",[328,2098,405],{}," is neither ",[328,2101,1698],{}," nor ",[328,2104,2105],{},"null"," after every focus-moving action. This bug throws no error on its own, so without the assertion it ships unnoticed—it is the single most valuable focus-restoration guard.",[324,2108,2109,2112,2113,2115,2116,2118,2119,2121,2122,2124],{},[348,2110,2111],{},"Where should focus go after a client-side route change?","\nTo a logical landmark in the new view—typically the page's ",[328,2114,320],{}," or the ",[328,2117,1469],{}," region, made focusable with ",[328,2120,1476],{},". Assert focus lands there, then press ",[328,2123,330],{}," once more to confirm the sequence continues inside the new page rather than restarting at the top.",[390,2126],{},[393,2128,2130],{"id":2129},"related-guides","Related guides",[352,2132,2133,2138,2143],{},[355,2134,2135,2137],{},[341,2136,279],{"href":343}," — the guide for scans, focus, and announcements.",[355,2139,2140,2142],{},[341,2141,25],{"href":1686}," — the framework patterns these assertions verify.",[355,2144,2145,2147],{},[341,2146,291],{"href":1172}," — tab, activate, and Escape flows that feed focus-order assertions.",[2149,2150,2151],"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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .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);}",{"title":436,"searchDepth":464,"depth":464,"links":2153},[2154,2155,2156,2157,2158,2159,2160,2161],{"id":395,"depth":464,"text":396},{"id":929,"depth":464,"text":930},{"id":1178,"depth":464,"text":1179},{"id":1442,"depth":464,"text":1443},{"id":1691,"depth":464,"text":1692},{"id":1998,"depth":464,"text":1999},{"id":2052,"depth":464,"text":2053},{"id":2129,"depth":464,"text":2130},null,"Lock down focus order and restoration in Playwright—capture the tab sequence, assert logical order, and verify focus returns correctly after modals and route changes.","md",{},false,{"title":285,"description":2163},"C-SMtbbqUuXDvBgXCHPQe4uO3s-ipwY64n6pmjlm9uQ",[2170,2209,2210,2273],{"title":5,"path":6,"stem":7,"children":2171},[2172,2173,2176,2179,2185,2191,2200,2206],{"title":10,"path":6,"stem":11},{"title":13,"path":14,"stem":15,"children":2174},[2175],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":2177},[2178],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":2180},[2181,2182],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":2183},[2184],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":2186},[2187,2188],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":2189},[2190],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":2192},[2193,2194,2197],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":2195},[2196],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":2198},[2199],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":2201},[2202,2203],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":2204},[2205],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":2207},[2208],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87},{"title":89,"path":90,"stem":91,"children":2211},[2212,2213,2219,2231,2243,2246,2255,2267],{"title":94,"path":90,"stem":95},{"title":97,"path":98,"stem":99,"children":2214},[2215,2216],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":2217},[2218],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":2220},[2221,2222,2225,2228],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":2223},[2224],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":2226},[2227],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":2229},[2230],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":2232},[2233,2234,2237,2240],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":2235},[2236],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":2238},[2239],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":2241},[2242],{"title":151,"path":152,"stem":153},{"title":157,"path":158,"stem":159,"children":2244},[2245],{"title":157,"path":158,"stem":159},{"title":163,"path":164,"stem":165,"children":2247},[2248,2249,2252],{"title":163,"path":164,"stem":165},{"title":169,"path":170,"stem":171,"children":2250},[2251],{"title":169,"path":170,"stem":171},{"title":175,"path":176,"stem":177,"children":2253},[2254],{"title":175,"path":176,"stem":177},{"title":181,"path":182,"stem":183,"children":2256},[2257,2258,2261,2264],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189,"children":2259},[2260],{"title":187,"path":188,"stem":189},{"title":193,"path":194,"stem":195,"children":2262},[2263],{"title":193,"path":194,"stem":195},{"title":199,"path":200,"stem":201,"children":2265},[2266],{"title":199,"path":200,"stem":201},{"title":205,"path":206,"stem":207,"children":2268},[2269,2270],{"title":205,"path":206,"stem":207},{"title":211,"path":212,"stem":213,"children":2271},[2272],{"title":211,"path":212,"stem":213},{"title":217,"path":218,"stem":219,"children":2274},[2275,2276,2285,2294,2303,2312],{"title":222,"path":218,"stem":223},{"title":225,"path":226,"stem":227,"children":2277},[2278,2279,2282],{"title":225,"path":226,"stem":227},{"title":231,"path":232,"stem":233,"children":2280},[2281],{"title":231,"path":232,"stem":233},{"title":237,"path":238,"stem":239,"children":2283},[2284],{"title":237,"path":238,"stem":239},{"title":243,"path":244,"stem":245,"children":2286},[2287,2288,2291],{"title":243,"path":244,"stem":245},{"title":249,"path":250,"stem":251,"children":2289},[2290],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257,"children":2292},[2293],{"title":255,"path":256,"stem":257},{"title":261,"path":262,"stem":263,"children":2295},[2296,2297,2300],{"title":261,"path":262,"stem":263},{"title":267,"path":268,"stem":269,"children":2298},[2299],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275,"children":2301},[2302],{"title":273,"path":274,"stem":275},{"title":279,"path":280,"stem":281,"children":2304},[2305,2306,2309],{"title":279,"path":280,"stem":281},{"title":285,"path":286,"stem":287,"children":2307},[2308],{"title":285,"path":286,"stem":287},{"title":291,"path":292,"stem":293,"children":2310},[2311],{"title":291,"path":292,"stem":293},{"title":297,"path":298,"stem":299,"children":2313},[2314,2315,2318],{"title":297,"path":298,"stem":299},{"title":303,"path":304,"stem":305,"children":2316},[2317],{"title":303,"path":304,"stem":305},{"title":309,"path":310,"stem":311,"children":2319},[2320],{"title":309,"path":310,"stem":311},1781785524250]