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