[{"data":1,"prerenderedAt":2561},["ShallowReactive",2],{"site-header-nav":3,"page-\u002Ftesting-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002Ftesting-react-components-with-jest-axe\u002F":314,"content-navigation":2409},[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":273,"body":316,"date":2402,"description":2403,"extension":2404,"image":2402,"meta":2405,"modifiedAt":2402,"navigation":501,"noindex":2406,"path":274,"publishedAt":2402,"seo":2407,"stem":275,"updatedAt":2402,"__hash__":2408},"content\u002Ftesting-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002Ftesting-react-components-with-jest-axe\u002Findex.md",{"type":317,"value":318,"toc":2390},"minimark",[319,323,357,372,377,384,428,439,539,549,552,556,570,1099,1102,1575,1588,1590,1594,1610,1861,2137,2157,2159,2163,2166,2186,2202,2214,2216,2220,2272,2274,2278,2284,2286,2290,2310,2332,2358,2368,2372,2386],[320,321,273],"h1",{"id":322},"testing-react-components-with-jest-axe",[324,325,326,327,331,332,335,336,339,340,335,343,346,347,351,352,356],"p",{},"This is a hands-on walkthrough for adding accessibility assertions to real React components with ",[328,329,330],"code",{},"jest-axe",". We start from a clean setup, then write complete, copy-ready test files for two components where accessibility most often breaks: an accessible form with associated error messages, and a modal dialog with a proper ",[328,333,334],{},"role=\"dialog\""," and ",[328,337,338],{},"aria-modal",". Along the way we assert the contracts axe-core enforces—broken references, missing names—and the contracts it cannot, like whether ",[328,341,342],{},"aria-invalid",[328,344,345],{},"aria-describedby"," are wired to the ",[348,349,350],"em",{},"right"," field. This guide expands the patterns introduced in ",[353,354,261],"a",{"href":355},"\u002Ftesting-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002F"," into full, runnable specs.",[324,358,359,360,363,364,367,368,371],{},"The rules in play here are ",[328,361,362],{},"4.1.2 Name, Role, Value"," (every control has a name and exposed state), ",[328,365,366],{},"1.3.1 Info and Relationships"," (programmatic associations match the visible structure), and ",[328,369,370],{},"3.3.1 Error Identification"," (errors are tied to their fields). Axe checks the structure; your role and name queries check the meaning.",[373,374,376],"h2",{"id":375},"prerequisites","Prerequisites",[324,378,379,380,383],{},"You need a React project with Jest configured for the ",[328,381,382],{},"jsdom"," environment plus the following dev dependencies:",[385,386,391],"pre",{"className":387,"code":388,"language":389,"meta":390,"style":390},"language-bash shiki shiki-themes github-light github-dark","npm install --save-dev jest-axe @testing-library\u002Freact \\\n  @testing-library\u002Fuser-event @testing-library\u002Fjest-dom\n","bash","",[328,392,393,419],{"__ignoreMap":390},[394,395,398,402,406,410,413,416],"span",{"class":396,"line":397},"line",1,[394,399,401],{"class":400},"sScJk","npm",[394,403,405],{"class":404},"sZZnC"," install",[394,407,409],{"class":408},"sj4cs"," --save-dev",[394,411,412],{"class":404}," jest-axe",[394,414,415],{"class":404}," @testing-library\u002Freact",[394,417,418],{"class":408}," \\\n",[394,420,422,425],{"class":396,"line":421},2,[394,423,424],{"class":404},"  @testing-library\u002Fuser-event",[394,426,427],{"class":404}," @testing-library\u002Fjest-dom\n",[324,429,430,431,434,435,438],{},"Register the axe matcher and the DOM matchers once in a global setup file referenced by ",[328,432,433],{},"setupFilesAfterEach"," in ",[328,436,437],{},"jest.config.js",":",[385,440,444],{"className":441,"code":442,"language":443,"meta":390,"style":390},"language-ts shiki shiki-themes github-light github-dark","\u002F\u002F test-setup.ts\nimport '@testing-library\u002Fjest-dom';\nimport { toHaveNoViolations } from 'jest-axe';\nimport { cleanup } from '@testing-library\u002Freact';\n\nexpect.extend(toHaveNoViolations); \u002F\u002F makes expect(results).toHaveNoViolations() available\nafterEach(() => cleanup());         \u002F\u002F unmount between tests so axe never sees stale DOM\n","ts",[328,445,446,452,465,481,496,503,518],{"__ignoreMap":390},[394,447,448],{"class":396,"line":397},[394,449,451],{"class":450},"sJ8bj","\u002F\u002F test-setup.ts\n",[394,453,454,458,461],{"class":396,"line":421},[394,455,457],{"class":456},"szBVR","import",[394,459,460],{"class":404}," '@testing-library\u002Fjest-dom'",[394,462,464],{"class":463},"sVt8B",";\n",[394,466,468,470,473,476,479],{"class":396,"line":467},3,[394,469,457],{"class":456},[394,471,472],{"class":463}," { toHaveNoViolations } ",[394,474,475],{"class":456},"from",[394,477,478],{"class":404}," 'jest-axe'",[394,480,464],{"class":463},[394,482,484,486,489,491,494],{"class":396,"line":483},4,[394,485,457],{"class":456},[394,487,488],{"class":463}," { cleanup } ",[394,490,475],{"class":456},[394,492,493],{"class":404}," '@testing-library\u002Freact'",[394,495,464],{"class":463},[394,497,499],{"class":396,"line":498},5,[394,500,502],{"emptyLinePlaceholder":501},true,"\n",[394,504,506,509,512,515],{"class":396,"line":505},6,[394,507,508],{"class":463},"expect.",[394,510,511],{"class":400},"extend",[394,513,514],{"class":463},"(toHaveNoViolations); ",[394,516,517],{"class":450},"\u002F\u002F makes expect(results).toHaveNoViolations() available\n",[394,519,521,524,527,530,533,536],{"class":396,"line":520},7,[394,522,523],{"class":400},"afterEach",[394,525,526],{"class":463},"(() ",[394,528,529],{"class":456},"=>",[394,531,532],{"class":400}," cleanup",[394,534,535],{"class":463},"());         ",[394,537,538],{"class":450},"\u002F\u002F unmount between tests so axe never sees stale DOM\n",[324,540,541,542,545,546,548],{},"With that in place, every spec can render a component, run ",[328,543,544],{},"await axe(container)",", and assert the result. The ",[328,547,382],{}," environment is required because axe-core walks a live DOM tree.",[550,551],"hr",{},[373,553,555],{"id":554},"testing-an-accessible-form-for-violations","Testing an Accessible Form for Violations",[324,557,558,559,562,563,565,566,569],{},"Consider a sign-up form where each field has a ",[328,560,561],{},"\u003Clabel>",", and validation errors are surfaced through ",[328,564,345],{}," pointing at a live error element. The accessibility-critical behavior is the wiring: when a field is invalid, it must set ",[328,567,568],{},"aria-invalid=\"true\""," and reference an error node that actually exists and contains the message text.",[385,571,575],{"className":572,"code":573,"language":574,"meta":390,"style":390},"language-tsx shiki shiki-themes github-light github-dark","\u002F\u002F SignupForm.tsx (abridged to the a11y-relevant parts)\nexport function SignupForm({ onSubmit }: { onSubmit: (email: string) => void }) {\n  const [error, setError] = useState('');\n\n  function handleSubmit(e: React.FormEvent\u003CHTMLFormElement>) {\n    e.preventDefault();\n    const email = new FormData(e.currentTarget).get('email');\n    if (typeof email !== 'string' || !email.includes('@')) {\n      setError('Enter a valid email address.');\n      return;\n    }\n    setError('');\n    onSubmit(email);\n  }\n\n  return (\n    \u003Cform onSubmit={handleSubmit} noValidate>\n      \u003Clabel htmlFor=\"email\">Email\u003C\u002Flabel>\n      \u003Cinput\n        id=\"email\"\n        name=\"email\"\n        type=\"email\"\n        aria-invalid={error ? true : undefined}      \u002F\u002F exposes invalid state to AT\n        aria-describedby={error ? 'email-error' : undefined} \u002F\u002F links field → message\n      \u002F>\n      {error && (\n        \u003Cp id=\"email-error\" role=\"alert\">{error}\u003C\u002Fp>  \u002F* role=alert announces on appear *\u002F\n      )}\n      \u003Cbutton type=\"submit\">Create account\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  );\n}\n","tsx",[328,576,577,582,634,669,673,706,717,747,787,800,808,814,826,835,841,846,855,879,903,911,922,932,942,971,996,1002,1013,1048,1054,1077,1087,1093],{"__ignoreMap":390},[394,578,579],{"class":396,"line":397},[394,580,581],{"class":450},"\u002F\u002F SignupForm.tsx (abridged to the a11y-relevant parts)\n",[394,583,584,587,590,593,596,600,603,605,608,610,612,615,618,620,623,626,628,631],{"class":396,"line":421},[394,585,586],{"class":456},"export",[394,588,589],{"class":456}," function",[394,591,592],{"class":400}," SignupForm",[394,594,595],{"class":463},"({ ",[394,597,599],{"class":598},"s4XuR","onSubmit",[394,601,602],{"class":463}," }",[394,604,438],{"class":456},[394,606,607],{"class":463}," { ",[394,609,599],{"class":400},[394,611,438],{"class":456},[394,613,614],{"class":463}," (",[394,616,617],{"class":598},"email",[394,619,438],{"class":456},[394,621,622],{"class":408}," string",[394,624,625],{"class":463},") ",[394,627,529],{"class":456},[394,629,630],{"class":408}," void",[394,632,633],{"class":463}," }) {\n",[394,635,636,639,642,645,648,651,654,657,660,663,666],{"class":396,"line":467},[394,637,638],{"class":456},"  const",[394,640,641],{"class":463}," [",[394,643,644],{"class":408},"error",[394,646,647],{"class":463},", ",[394,649,650],{"class":408},"setError",[394,652,653],{"class":463},"] ",[394,655,656],{"class":456},"=",[394,658,659],{"class":400}," useState",[394,661,662],{"class":463},"(",[394,664,665],{"class":404},"''",[394,667,668],{"class":463},");\n",[394,670,671],{"class":396,"line":483},[394,672,502],{"emptyLinePlaceholder":501},[394,674,675,678,681,683,686,688,691,694,697,700,703],{"class":396,"line":498},[394,676,677],{"class":456},"  function",[394,679,680],{"class":400}," handleSubmit",[394,682,662],{"class":463},[394,684,685],{"class":598},"e",[394,687,438],{"class":456},[394,689,690],{"class":400}," React",[394,692,693],{"class":463},".",[394,695,696],{"class":400},"FormEvent",[394,698,699],{"class":463},"\u003C",[394,701,702],{"class":400},"HTMLFormElement",[394,704,705],{"class":463},">) {\n",[394,707,708,711,714],{"class":396,"line":505},[394,709,710],{"class":463},"    e.",[394,712,713],{"class":400},"preventDefault",[394,715,716],{"class":463},"();\n",[394,718,719,722,725,728,731,734,737,740,742,745],{"class":396,"line":520},[394,720,721],{"class":456},"    const",[394,723,724],{"class":408}," email",[394,726,727],{"class":456}," =",[394,729,730],{"class":456}," new",[394,732,733],{"class":400}," FormData",[394,735,736],{"class":463},"(e.currentTarget).",[394,738,739],{"class":400},"get",[394,741,662],{"class":463},[394,743,744],{"class":404},"'email'",[394,746,668],{"class":463},[394,748,750,753,755,758,761,764,767,770,773,776,779,781,784],{"class":396,"line":749},8,[394,751,752],{"class":456},"    if",[394,754,614],{"class":463},[394,756,757],{"class":456},"typeof",[394,759,760],{"class":463}," email ",[394,762,763],{"class":456},"!==",[394,765,766],{"class":404}," 'string'",[394,768,769],{"class":456}," ||",[394,771,772],{"class":456}," !",[394,774,775],{"class":463},"email.",[394,777,778],{"class":400},"includes",[394,780,662],{"class":463},[394,782,783],{"class":404},"'@'",[394,785,786],{"class":463},")) {\n",[394,788,790,793,795,798],{"class":396,"line":789},9,[394,791,792],{"class":400},"      setError",[394,794,662],{"class":463},[394,796,797],{"class":404},"'Enter a valid email address.'",[394,799,668],{"class":463},[394,801,803,806],{"class":396,"line":802},10,[394,804,805],{"class":456},"      return",[394,807,464],{"class":463},[394,809,811],{"class":396,"line":810},11,[394,812,813],{"class":463},"    }\n",[394,815,817,820,822,824],{"class":396,"line":816},12,[394,818,819],{"class":400},"    setError",[394,821,662],{"class":463},[394,823,665],{"class":404},[394,825,668],{"class":463},[394,827,829,832],{"class":396,"line":828},13,[394,830,831],{"class":400},"    onSubmit",[394,833,834],{"class":463},"(email);\n",[394,836,838],{"class":396,"line":837},14,[394,839,840],{"class":463},"  }\n",[394,842,844],{"class":396,"line":843},15,[394,845,502],{"emptyLinePlaceholder":501},[394,847,849,852],{"class":396,"line":848},16,[394,850,851],{"class":456},"  return",[394,853,854],{"class":463}," (\n",[394,856,858,861,865,868,870,873,876],{"class":396,"line":857},17,[394,859,860],{"class":463},"    \u003C",[394,862,864],{"class":863},"s9eBZ","form",[394,866,867],{"class":400}," onSubmit",[394,869,656],{"class":456},[394,871,872],{"class":463},"{handleSubmit} ",[394,874,875],{"class":400},"noValidate",[394,877,878],{"class":463},">\n",[394,880,882,885,888,891,893,896,899,901],{"class":396,"line":881},18,[394,883,884],{"class":463},"      \u003C",[394,886,887],{"class":863},"label",[394,889,890],{"class":400}," htmlFor",[394,892,656],{"class":456},[394,894,895],{"class":404},"\"email\"",[394,897,898],{"class":463},">Email\u003C\u002F",[394,900,887],{"class":863},[394,902,878],{"class":463},[394,904,906,908],{"class":396,"line":905},19,[394,907,884],{"class":463},[394,909,910],{"class":863},"input\n",[394,912,914,917,919],{"class":396,"line":913},20,[394,915,916],{"class":400},"        id",[394,918,656],{"class":456},[394,920,921],{"class":404},"\"email\"\n",[394,923,925,928,930],{"class":396,"line":924},21,[394,926,927],{"class":400},"        name",[394,929,656],{"class":456},[394,931,921],{"class":404},[394,933,935,938,940],{"class":396,"line":934},22,[394,936,937],{"class":400},"        type",[394,939,656],{"class":456},[394,941,921],{"class":404},[394,943,945,948,950,953,956,959,962,965,968],{"class":396,"line":944},23,[394,946,947],{"class":400},"        aria-invalid",[394,949,656],{"class":456},[394,951,952],{"class":463},"{error ",[394,954,955],{"class":456},"?",[394,957,958],{"class":408}," true",[394,960,961],{"class":456}," :",[394,963,964],{"class":408}," undefined",[394,966,967],{"class":463},"}      ",[394,969,970],{"class":450},"\u002F\u002F exposes invalid state to AT\n",[394,972,974,977,979,981,983,986,988,990,993],{"class":396,"line":973},24,[394,975,976],{"class":400},"        aria-describedby",[394,978,656],{"class":456},[394,980,952],{"class":463},[394,982,955],{"class":456},[394,984,985],{"class":404}," 'email-error'",[394,987,961],{"class":456},[394,989,964],{"class":408},[394,991,992],{"class":463},"} ",[394,994,995],{"class":450},"\u002F\u002F links field → message\n",[394,997,999],{"class":396,"line":998},25,[394,1000,1001],{"class":463},"      \u002F>\n",[394,1003,1005,1008,1011],{"class":396,"line":1004},26,[394,1006,1007],{"class":463},"      {error ",[394,1009,1010],{"class":456},"&&",[394,1012,854],{"class":463},[394,1014,1016,1019,1021,1024,1026,1029,1032,1034,1037,1040,1042,1045],{"class":396,"line":1015},27,[394,1017,1018],{"class":463},"        \u003C",[394,1020,324],{"class":863},[394,1022,1023],{"class":400}," id",[394,1025,656],{"class":456},[394,1027,1028],{"class":404},"\"email-error\"",[394,1030,1031],{"class":400}," role",[394,1033,656],{"class":456},[394,1035,1036],{"class":404},"\"alert\"",[394,1038,1039],{"class":463},">{error}\u003C\u002F",[394,1041,324],{"class":863},[394,1043,1044],{"class":463},">  ",[394,1046,1047],{"class":450},"\u002F* role=alert announces on appear *\u002F\n",[394,1049,1051],{"class":396,"line":1050},28,[394,1052,1053],{"class":463},"      )}\n",[394,1055,1057,1059,1062,1065,1067,1070,1073,1075],{"class":396,"line":1056},29,[394,1058,884],{"class":463},[394,1060,1061],{"class":863},"button",[394,1063,1064],{"class":400}," type",[394,1066,656],{"class":456},[394,1068,1069],{"class":404},"\"submit\"",[394,1071,1072],{"class":463},">Create account\u003C\u002F",[394,1074,1061],{"class":863},[394,1076,878],{"class":463},[394,1078,1080,1083,1085],{"class":396,"line":1079},30,[394,1081,1082],{"class":463},"    \u003C\u002F",[394,1084,864],{"class":863},[394,1086,878],{"class":463},[394,1088,1090],{"class":396,"line":1089},31,[394,1091,1092],{"class":463},"  );\n",[394,1094,1096],{"class":396,"line":1095},32,[394,1097,1098],{"class":463},"}\n",[324,1100,1101],{},"The test renders the form, runs axe at rest, then drives it into its error state and runs axe again—two distinct DOM states, two passes:",[385,1103,1105],{"className":572,"code":1104,"language":574,"meta":390,"style":390},"\u002F\u002F SignupForm.test.tsx\nimport { render, screen } from '@testing-library\u002Freact';\nimport userEvent from '@testing-library\u002Fuser-event';\nimport { axe } from 'jest-axe';\nimport { SignupForm } from '.\u002FSignupForm';\n\ntest('form is accessible at rest', async () => {\n  const { container } = render(\u003CSignupForm onSubmit={jest.fn()} \u002F>);\n  \u002F\u002F Catches a missing label or an input with no accessible name (4.1.2)\n  expect(await axe(container)).toHaveNoViolations();\n});\n\ntest('invalid submit wires aria-invalid and aria-describedby to the field', async () => {\n  const user = userEvent.setup();\n  const { container } = render(\u003CSignupForm onSubmit={jest.fn()} \u002F>);\n\n  await user.type(screen.getByRole('textbox', { name: \u002Femail\u002Fi }), 'not-an-email');\n  await user.click(screen.getByRole('button', { name: \u002Fcreate account\u002Fi }));\n\n  \u002F\u002F The error node mounts asynchronously after state update—wait for it\n  const error = await screen.findByRole('alert');\n  expect(error).toHaveTextContent(\u002Fenter a valid email\u002Fi);\n\n  \u002F\u002F Re-run axe on the ERROR state: confirms aria-describedby resolves to a real id\n  expect(await axe(container)).toHaveNoViolations();\n\n  \u002F\u002F Assert the semantic contract axe cannot judge: invalid state + the RIGHT description\n  const email = screen.getByRole('textbox', { name: \u002Femail\u002Fi });\n  expect(email).toHaveAttribute('aria-invalid', 'true');\n  expect(email).toHaveAccessibleDescription(\u002Fenter a valid email\u002Fi);\n});\n",[328,1106,1107,1112,1125,1139,1152,1166,1170,1193,1229,1234,1255,1260,1264,1283,1300,1328,1332,1376,1408,1412,1417,1442,1465,1469,1474,1490,1494,1499,1528,1550,1571],{"__ignoreMap":390},[394,1108,1109],{"class":396,"line":397},[394,1110,1111],{"class":450},"\u002F\u002F SignupForm.test.tsx\n",[394,1113,1114,1116,1119,1121,1123],{"class":396,"line":421},[394,1115,457],{"class":456},[394,1117,1118],{"class":463}," { render, screen } ",[394,1120,475],{"class":456},[394,1122,493],{"class":404},[394,1124,464],{"class":463},[394,1126,1127,1129,1132,1134,1137],{"class":396,"line":467},[394,1128,457],{"class":456},[394,1130,1131],{"class":463}," userEvent ",[394,1133,475],{"class":456},[394,1135,1136],{"class":404}," '@testing-library\u002Fuser-event'",[394,1138,464],{"class":463},[394,1140,1141,1143,1146,1148,1150],{"class":396,"line":483},[394,1142,457],{"class":456},[394,1144,1145],{"class":463}," { axe } ",[394,1147,475],{"class":456},[394,1149,478],{"class":404},[394,1151,464],{"class":463},[394,1153,1154,1156,1159,1161,1164],{"class":396,"line":498},[394,1155,457],{"class":456},[394,1157,1158],{"class":463}," { SignupForm } ",[394,1160,475],{"class":456},[394,1162,1163],{"class":404}," '.\u002FSignupForm'",[394,1165,464],{"class":463},[394,1167,1168],{"class":396,"line":505},[394,1169,502],{"emptyLinePlaceholder":501},[394,1171,1172,1175,1177,1180,1182,1185,1188,1190],{"class":396,"line":520},[394,1173,1174],{"class":400},"test",[394,1176,662],{"class":463},[394,1178,1179],{"class":404},"'form is accessible at rest'",[394,1181,647],{"class":463},[394,1183,1184],{"class":456},"async",[394,1186,1187],{"class":463}," () ",[394,1189,529],{"class":456},[394,1191,1192],{"class":463}," {\n",[394,1194,1195,1197,1199,1202,1205,1207,1210,1213,1216,1218,1220,1223,1226],{"class":396,"line":749},[394,1196,638],{"class":456},[394,1198,607],{"class":463},[394,1200,1201],{"class":408},"container",[394,1203,1204],{"class":463}," } ",[394,1206,656],{"class":456},[394,1208,1209],{"class":400}," render",[394,1211,1212],{"class":463},"(\u003C",[394,1214,1215],{"class":408},"SignupForm",[394,1217,867],{"class":400},[394,1219,656],{"class":456},[394,1221,1222],{"class":463},"{jest.",[394,1224,1225],{"class":400},"fn",[394,1227,1228],{"class":463},"()} \u002F>);\n",[394,1230,1231],{"class":396,"line":789},[394,1232,1233],{"class":450},"  \u002F\u002F Catches a missing label or an input with no accessible name (4.1.2)\n",[394,1235,1236,1239,1241,1244,1247,1250,1253],{"class":396,"line":802},[394,1237,1238],{"class":400},"  expect",[394,1240,662],{"class":463},[394,1242,1243],{"class":456},"await",[394,1245,1246],{"class":400}," axe",[394,1248,1249],{"class":463},"(container)).",[394,1251,1252],{"class":400},"toHaveNoViolations",[394,1254,716],{"class":463},[394,1256,1257],{"class":396,"line":810},[394,1258,1259],{"class":463},"});\n",[394,1261,1262],{"class":396,"line":816},[394,1263,502],{"emptyLinePlaceholder":501},[394,1265,1266,1268,1270,1273,1275,1277,1279,1281],{"class":396,"line":828},[394,1267,1174],{"class":400},[394,1269,662],{"class":463},[394,1271,1272],{"class":404},"'invalid submit wires aria-invalid and aria-describedby to the field'",[394,1274,647],{"class":463},[394,1276,1184],{"class":456},[394,1278,1187],{"class":463},[394,1280,529],{"class":456},[394,1282,1192],{"class":463},[394,1284,1285,1287,1290,1292,1295,1298],{"class":396,"line":837},[394,1286,638],{"class":456},[394,1288,1289],{"class":408}," user",[394,1291,727],{"class":456},[394,1293,1294],{"class":463}," userEvent.",[394,1296,1297],{"class":400},"setup",[394,1299,716],{"class":463},[394,1301,1302,1304,1306,1308,1310,1312,1314,1316,1318,1320,1322,1324,1326],{"class":396,"line":843},[394,1303,638],{"class":456},[394,1305,607],{"class":463},[394,1307,1201],{"class":408},[394,1309,1204],{"class":463},[394,1311,656],{"class":456},[394,1313,1209],{"class":400},[394,1315,1212],{"class":463},[394,1317,1215],{"class":408},[394,1319,867],{"class":400},[394,1321,656],{"class":456},[394,1323,1222],{"class":463},[394,1325,1225],{"class":400},[394,1327,1228],{"class":463},[394,1329,1330],{"class":396,"line":848},[394,1331,502],{"emptyLinePlaceholder":501},[394,1333,1334,1337,1340,1343,1346,1349,1351,1354,1357,1360,1363,1365,1368,1371,1374],{"class":396,"line":857},[394,1335,1336],{"class":456},"  await",[394,1338,1339],{"class":463}," user.",[394,1341,1342],{"class":400},"type",[394,1344,1345],{"class":463},"(screen.",[394,1347,1348],{"class":400},"getByRole",[394,1350,662],{"class":463},[394,1352,1353],{"class":404},"'textbox'",[394,1355,1356],{"class":463},", { name:",[394,1358,1359],{"class":404}," \u002F",[394,1361,617],{"class":1362},"sA_wV",[394,1364,86],{"class":404},[394,1366,1367],{"class":456},"i",[394,1369,1370],{"class":463}," }), ",[394,1372,1373],{"class":404},"'not-an-email'",[394,1375,668],{"class":463},[394,1377,1378,1380,1382,1385,1387,1389,1391,1394,1396,1398,1401,1403,1405],{"class":396,"line":881},[394,1379,1336],{"class":456},[394,1381,1339],{"class":463},[394,1383,1384],{"class":400},"click",[394,1386,1345],{"class":463},[394,1388,1348],{"class":400},[394,1390,662],{"class":463},[394,1392,1393],{"class":404},"'button'",[394,1395,1356],{"class":463},[394,1397,1359],{"class":404},[394,1399,1400],{"class":1362},"create account",[394,1402,86],{"class":404},[394,1404,1367],{"class":456},[394,1406,1407],{"class":463}," }));\n",[394,1409,1410],{"class":396,"line":905},[394,1411,502],{"emptyLinePlaceholder":501},[394,1413,1414],{"class":396,"line":913},[394,1415,1416],{"class":450},"  \u002F\u002F The error node mounts asynchronously after state update—wait for it\n",[394,1418,1419,1421,1424,1426,1429,1432,1435,1437,1440],{"class":396,"line":924},[394,1420,638],{"class":456},[394,1422,1423],{"class":408}," error",[394,1425,727],{"class":456},[394,1427,1428],{"class":456}," await",[394,1430,1431],{"class":463}," screen.",[394,1433,1434],{"class":400},"findByRole",[394,1436,662],{"class":463},[394,1438,1439],{"class":404},"'alert'",[394,1441,668],{"class":463},[394,1443,1444,1446,1449,1452,1454,1456,1459,1461,1463],{"class":396,"line":934},[394,1445,1238],{"class":400},[394,1447,1448],{"class":463},"(error).",[394,1450,1451],{"class":400},"toHaveTextContent",[394,1453,662],{"class":463},[394,1455,86],{"class":404},[394,1457,1458],{"class":1362},"enter a valid email",[394,1460,86],{"class":404},[394,1462,1367],{"class":456},[394,1464,668],{"class":463},[394,1466,1467],{"class":396,"line":944},[394,1468,502],{"emptyLinePlaceholder":501},[394,1470,1471],{"class":396,"line":973},[394,1472,1473],{"class":450},"  \u002F\u002F Re-run axe on the ERROR state: confirms aria-describedby resolves to a real id\n",[394,1475,1476,1478,1480,1482,1484,1486,1488],{"class":396,"line":998},[394,1477,1238],{"class":400},[394,1479,662],{"class":463},[394,1481,1243],{"class":456},[394,1483,1246],{"class":400},[394,1485,1249],{"class":463},[394,1487,1252],{"class":400},[394,1489,716],{"class":463},[394,1491,1492],{"class":396,"line":1004},[394,1493,502],{"emptyLinePlaceholder":501},[394,1495,1496],{"class":396,"line":1015},[394,1497,1498],{"class":450},"  \u002F\u002F Assert the semantic contract axe cannot judge: invalid state + the RIGHT description\n",[394,1500,1501,1503,1505,1507,1509,1511,1513,1515,1517,1519,1521,1523,1525],{"class":396,"line":1050},[394,1502,638],{"class":456},[394,1504,724],{"class":408},[394,1506,727],{"class":456},[394,1508,1431],{"class":463},[394,1510,1348],{"class":400},[394,1512,662],{"class":463},[394,1514,1353],{"class":404},[394,1516,1356],{"class":463},[394,1518,1359],{"class":404},[394,1520,617],{"class":1362},[394,1522,86],{"class":404},[394,1524,1367],{"class":456},[394,1526,1527],{"class":463}," });\n",[394,1529,1530,1532,1535,1538,1540,1543,1545,1548],{"class":396,"line":1056},[394,1531,1238],{"class":400},[394,1533,1534],{"class":463},"(email).",[394,1536,1537],{"class":400},"toHaveAttribute",[394,1539,662],{"class":463},[394,1541,1542],{"class":404},"'aria-invalid'",[394,1544,647],{"class":463},[394,1546,1547],{"class":404},"'true'",[394,1549,668],{"class":463},[394,1551,1552,1554,1556,1559,1561,1563,1565,1567,1569],{"class":396,"line":1079},[394,1553,1238],{"class":400},[394,1555,1534],{"class":463},[394,1557,1558],{"class":400},"toHaveAccessibleDescription",[394,1560,662],{"class":463},[394,1562,86],{"class":404},[394,1564,1458],{"class":1362},[394,1566,86],{"class":404},[394,1568,1367],{"class":456},[394,1570,668],{"class":463},[394,1572,1573],{"class":396,"line":1089},[394,1574,1259],{"class":463},[324,1576,1577,1578,1580,1581,1583,1584,693],{},"If you removed the ",[328,1579,345],{},", axe would still pass (the field is otherwise valid), but ",[328,1582,1558],{}," would fail—demonstrating exactly why structural and semantic assertions must travel together. For the component-side patterns behind this wiring, see ",[353,1585,1587],{"href":1586},"\u002Freact-nextjs-accessibility-patterns\u002Fform-handling-with-react-hook-form-a11y\u002F","Form Handling with React Hook Form a11y",[550,1589],{},[373,1591,1593],{"id":1592},"testing-a-modal-for-violations","Testing a Modal for Violations",[324,1595,1596,1597,1599,1600,1603,1604,1607,1608,693],{},"A modal must expose ",[328,1598,334],{},", set ",[328,1601,1602],{},"aria-modal=\"true\"",", and carry an accessible name—typically via ",[328,1605,1606],{},"aria-labelledby"," pointing at its heading. Axe verifies the name reference resolves; you assert the dialog opens, is named correctly, and carries ",[328,1609,338],{},[385,1611,1613],{"className":572,"code":1612,"language":574,"meta":390,"style":390},"\u002F\u002F ConfirmDialog.tsx (abridged)\nexport function ConfirmDialog() {\n  const [open, setOpen] = useState(false);\n  return (\n    \u003C>\n      \u003Cbutton onClick={() => setOpen(true)}>Delete project\u003C\u002Fbutton>\n      {open && (\n        \u003Cdiv role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"dlg-title\">\n          \u003Ch2 id=\"dlg-title\">Delete project?\u003C\u002Fh2>       {\u002F* supplies the dialog name *\u002F}\n          \u003Cp>This action cannot be undone.\u003C\u002Fp>\n          \u003Cbutton onClick={() => setOpen(false)}>Cancel\u003C\u002Fbutton>\n          \u003Cbutton onClick={() => setOpen(false)}>Delete\u003C\u002Fbutton>\n        \u003C\u002Fdiv>\n      )}\n    \u003C\u002F>\n  );\n}\n",[328,1614,1615,1620,1632,1659,1665,1670,1701,1710,1742,1768,1781,1808,1835,1844,1848,1853,1857],{"__ignoreMap":390},[394,1616,1617],{"class":396,"line":397},[394,1618,1619],{"class":450},"\u002F\u002F ConfirmDialog.tsx (abridged)\n",[394,1621,1622,1624,1626,1629],{"class":396,"line":421},[394,1623,586],{"class":456},[394,1625,589],{"class":456},[394,1627,1628],{"class":400}," ConfirmDialog",[394,1630,1631],{"class":463},"() {\n",[394,1633,1634,1636,1638,1641,1643,1646,1648,1650,1652,1654,1657],{"class":396,"line":467},[394,1635,638],{"class":456},[394,1637,641],{"class":463},[394,1639,1640],{"class":408},"open",[394,1642,647],{"class":463},[394,1644,1645],{"class":408},"setOpen",[394,1647,653],{"class":463},[394,1649,656],{"class":456},[394,1651,659],{"class":400},[394,1653,662],{"class":463},[394,1655,1656],{"class":408},"false",[394,1658,668],{"class":463},[394,1660,1661,1663],{"class":396,"line":483},[394,1662,851],{"class":456},[394,1664,854],{"class":463},[394,1666,1667],{"class":396,"line":498},[394,1668,1669],{"class":463},"    \u003C>\n",[394,1671,1672,1674,1676,1679,1681,1684,1686,1689,1691,1694,1697,1699],{"class":396,"line":505},[394,1673,884],{"class":463},[394,1675,1061],{"class":863},[394,1677,1678],{"class":400}," onClick",[394,1680,656],{"class":456},[394,1682,1683],{"class":463},"{() ",[394,1685,529],{"class":456},[394,1687,1688],{"class":400}," setOpen",[394,1690,662],{"class":463},[394,1692,1693],{"class":408},"true",[394,1695,1696],{"class":463},")}>Delete project\u003C\u002F",[394,1698,1061],{"class":863},[394,1700,878],{"class":463},[394,1702,1703,1706,1708],{"class":396,"line":520},[394,1704,1705],{"class":463},"      {open ",[394,1707,1010],{"class":456},[394,1709,854],{"class":463},[394,1711,1712,1714,1717,1719,1721,1724,1727,1729,1732,1735,1737,1740],{"class":396,"line":749},[394,1713,1018],{"class":463},[394,1715,1716],{"class":863},"div",[394,1718,1031],{"class":400},[394,1720,656],{"class":456},[394,1722,1723],{"class":404},"\"dialog\"",[394,1725,1726],{"class":400}," aria-modal",[394,1728,656],{"class":456},[394,1730,1731],{"class":404},"\"true\"",[394,1733,1734],{"class":400}," aria-labelledby",[394,1736,656],{"class":456},[394,1738,1739],{"class":404},"\"dlg-title\"",[394,1741,878],{"class":463},[394,1743,1744,1747,1749,1751,1753,1755,1758,1760,1763,1766],{"class":396,"line":789},[394,1745,1746],{"class":463},"          \u003C",[394,1748,373],{"class":863},[394,1750,1023],{"class":400},[394,1752,656],{"class":456},[394,1754,1739],{"class":404},[394,1756,1757],{"class":463},">Delete project?\u003C\u002F",[394,1759,373],{"class":863},[394,1761,1762],{"class":463},">       {",[394,1764,1765],{"class":450},"\u002F* supplies the dialog name *\u002F",[394,1767,1098],{"class":463},[394,1769,1770,1772,1774,1777,1779],{"class":396,"line":802},[394,1771,1746],{"class":463},[394,1773,324],{"class":863},[394,1775,1776],{"class":463},">This action cannot be undone.\u003C\u002F",[394,1778,324],{"class":863},[394,1780,878],{"class":463},[394,1782,1783,1785,1787,1789,1791,1793,1795,1797,1799,1801,1804,1806],{"class":396,"line":810},[394,1784,1746],{"class":463},[394,1786,1061],{"class":863},[394,1788,1678],{"class":400},[394,1790,656],{"class":456},[394,1792,1683],{"class":463},[394,1794,529],{"class":456},[394,1796,1688],{"class":400},[394,1798,662],{"class":463},[394,1800,1656],{"class":408},[394,1802,1803],{"class":463},")}>Cancel\u003C\u002F",[394,1805,1061],{"class":863},[394,1807,878],{"class":463},[394,1809,1810,1812,1814,1816,1818,1820,1822,1824,1826,1828,1831,1833],{"class":396,"line":816},[394,1811,1746],{"class":463},[394,1813,1061],{"class":863},[394,1815,1678],{"class":400},[394,1817,656],{"class":456},[394,1819,1683],{"class":463},[394,1821,529],{"class":456},[394,1823,1688],{"class":400},[394,1825,662],{"class":463},[394,1827,1656],{"class":408},[394,1829,1830],{"class":463},")}>Delete\u003C\u002F",[394,1832,1061],{"class":863},[394,1834,878],{"class":463},[394,1836,1837,1840,1842],{"class":396,"line":828},[394,1838,1839],{"class":463},"        \u003C\u002F",[394,1841,1716],{"class":863},[394,1843,878],{"class":463},[394,1845,1846],{"class":396,"line":837},[394,1847,1053],{"class":463},[394,1849,1850],{"class":396,"line":843},[394,1851,1852],{"class":463},"    \u003C\u002F>\n",[394,1854,1855],{"class":396,"line":848},[394,1856,1092],{"class":463},[394,1858,1859],{"class":396,"line":857},[394,1860,1098],{"class":463},[385,1862,1864],{"className":572,"code":1863,"language":574,"meta":390,"style":390},"\u002F\u002F ConfirmDialog.test.tsx\nimport { render, screen } from '@testing-library\u002Freact';\nimport userEvent from '@testing-library\u002Fuser-event';\nimport { axe } from 'jest-axe';\nimport { ConfirmDialog } from '.\u002FConfirmDialog';\n\ntest('open dialog has no violations and a correct dialog contract', async () => {\n  const user = userEvent.setup();\n  const { container } = render(\u003CConfirmDialog \u002F>);\n\n  \u002F\u002F Drive into the OPEN state—the dialog does not exist on initial render\n  await user.click(screen.getByRole('button', { name: \u002Fdelete project\u002Fi }));\n\n  \u002F\u002F findByRole waits for the dialog to mount before axe and assertions run\n  const dialog = await screen.findByRole('dialog', { name: \u002Fdelete project\\?\u002Fi });\n\n  \u002F\u002F axe confirms aria-labelledby resolves and the structure is legal (1.3.1, 4.1.2)\n  expect(await axe(container)).toHaveNoViolations();\n\n  \u002F\u002F Assert the contracts axe will not enforce: modal flag + accessible name\n  expect(dialog).toHaveAttribute('aria-modal', 'true');\n  expect(dialog).toHaveAccessibleName('Delete project?');\n});\n",[328,1865,1866,1871,1883,1895,1907,1921,1925,1944,1958,1980,1984,1989,2018,2022,2027,2063,2067,2072,2088,2092,2097,2117,2133],{"__ignoreMap":390},[394,1867,1868],{"class":396,"line":397},[394,1869,1870],{"class":450},"\u002F\u002F ConfirmDialog.test.tsx\n",[394,1872,1873,1875,1877,1879,1881],{"class":396,"line":421},[394,1874,457],{"class":456},[394,1876,1118],{"class":463},[394,1878,475],{"class":456},[394,1880,493],{"class":404},[394,1882,464],{"class":463},[394,1884,1885,1887,1889,1891,1893],{"class":396,"line":467},[394,1886,457],{"class":456},[394,1888,1131],{"class":463},[394,1890,475],{"class":456},[394,1892,1136],{"class":404},[394,1894,464],{"class":463},[394,1896,1897,1899,1901,1903,1905],{"class":396,"line":483},[394,1898,457],{"class":456},[394,1900,1145],{"class":463},[394,1902,475],{"class":456},[394,1904,478],{"class":404},[394,1906,464],{"class":463},[394,1908,1909,1911,1914,1916,1919],{"class":396,"line":498},[394,1910,457],{"class":456},[394,1912,1913],{"class":463}," { ConfirmDialog } ",[394,1915,475],{"class":456},[394,1917,1918],{"class":404}," '.\u002FConfirmDialog'",[394,1920,464],{"class":463},[394,1922,1923],{"class":396,"line":505},[394,1924,502],{"emptyLinePlaceholder":501},[394,1926,1927,1929,1931,1934,1936,1938,1940,1942],{"class":396,"line":520},[394,1928,1174],{"class":400},[394,1930,662],{"class":463},[394,1932,1933],{"class":404},"'open dialog has no violations and a correct dialog contract'",[394,1935,647],{"class":463},[394,1937,1184],{"class":456},[394,1939,1187],{"class":463},[394,1941,529],{"class":456},[394,1943,1192],{"class":463},[394,1945,1946,1948,1950,1952,1954,1956],{"class":396,"line":749},[394,1947,638],{"class":456},[394,1949,1289],{"class":408},[394,1951,727],{"class":456},[394,1953,1294],{"class":463},[394,1955,1297],{"class":400},[394,1957,716],{"class":463},[394,1959,1960,1962,1964,1966,1968,1970,1972,1974,1977],{"class":396,"line":789},[394,1961,638],{"class":456},[394,1963,607],{"class":463},[394,1965,1201],{"class":408},[394,1967,1204],{"class":463},[394,1969,656],{"class":456},[394,1971,1209],{"class":400},[394,1973,1212],{"class":463},[394,1975,1976],{"class":408},"ConfirmDialog",[394,1978,1979],{"class":463}," \u002F>);\n",[394,1981,1982],{"class":396,"line":802},[394,1983,502],{"emptyLinePlaceholder":501},[394,1985,1986],{"class":396,"line":810},[394,1987,1988],{"class":450},"  \u002F\u002F Drive into the OPEN state—the dialog does not exist on initial render\n",[394,1990,1991,1993,1995,1997,1999,2001,2003,2005,2007,2009,2012,2014,2016],{"class":396,"line":816},[394,1992,1336],{"class":456},[394,1994,1339],{"class":463},[394,1996,1384],{"class":400},[394,1998,1345],{"class":463},[394,2000,1348],{"class":400},[394,2002,662],{"class":463},[394,2004,1393],{"class":404},[394,2006,1356],{"class":463},[394,2008,1359],{"class":404},[394,2010,2011],{"class":1362},"delete project",[394,2013,86],{"class":404},[394,2015,1367],{"class":456},[394,2017,1407],{"class":463},[394,2019,2020],{"class":396,"line":828},[394,2021,502],{"emptyLinePlaceholder":501},[394,2023,2024],{"class":396,"line":837},[394,2025,2026],{"class":450},"  \u002F\u002F findByRole waits for the dialog to mount before axe and assertions run\n",[394,2028,2029,2031,2034,2036,2038,2040,2042,2044,2047,2049,2051,2053,2057,2059,2061],{"class":396,"line":843},[394,2030,638],{"class":456},[394,2032,2033],{"class":408}," dialog",[394,2035,727],{"class":456},[394,2037,1428],{"class":456},[394,2039,1431],{"class":463},[394,2041,1434],{"class":400},[394,2043,662],{"class":463},[394,2045,2046],{"class":404},"'dialog'",[394,2048,1356],{"class":463},[394,2050,1359],{"class":404},[394,2052,2011],{"class":1362},[394,2054,2056],{"class":2055},"snhLl","\\?",[394,2058,86],{"class":404},[394,2060,1367],{"class":456},[394,2062,1527],{"class":463},[394,2064,2065],{"class":396,"line":848},[394,2066,502],{"emptyLinePlaceholder":501},[394,2068,2069],{"class":396,"line":857},[394,2070,2071],{"class":450},"  \u002F\u002F axe confirms aria-labelledby resolves and the structure is legal (1.3.1, 4.1.2)\n",[394,2073,2074,2076,2078,2080,2082,2084,2086],{"class":396,"line":881},[394,2075,1238],{"class":400},[394,2077,662],{"class":463},[394,2079,1243],{"class":456},[394,2081,1246],{"class":400},[394,2083,1249],{"class":463},[394,2085,1252],{"class":400},[394,2087,716],{"class":463},[394,2089,2090],{"class":396,"line":905},[394,2091,502],{"emptyLinePlaceholder":501},[394,2093,2094],{"class":396,"line":913},[394,2095,2096],{"class":450},"  \u002F\u002F Assert the contracts axe will not enforce: modal flag + accessible name\n",[394,2098,2099,2101,2104,2106,2108,2111,2113,2115],{"class":396,"line":924},[394,2100,1238],{"class":400},[394,2102,2103],{"class":463},"(dialog).",[394,2105,1537],{"class":400},[394,2107,662],{"class":463},[394,2109,2110],{"class":404},"'aria-modal'",[394,2112,647],{"class":463},[394,2114,1547],{"class":404},[394,2116,668],{"class":463},[394,2118,2119,2121,2123,2126,2128,2131],{"class":396,"line":934},[394,2120,1238],{"class":400},[394,2122,2103],{"class":463},[394,2124,2125],{"class":400},"toHaveAccessibleName",[394,2127,662],{"class":463},[394,2129,2130],{"class":404},"'Delete project?'",[394,2132,668],{"class":463},[394,2134,2135],{"class":396,"line":944},[394,2136,1259],{"class":463},[324,2138,2139,2140,2143,2144,2147,2148,2150,2151,2153,2154,693],{},"Querying ",[328,2141,2142],{},"getByRole('dialog', { name: ... })"," does double duty: it fails if the dialog is missing ",[348,2145,2146],{},"and"," if its accessible name is wrong, so a single query guards both the role and the name from ",[328,2149,362],{},". If your dialog renders through a React portal, axe scoped to ",[328,2152,1201],{}," will skip it—handle that scope problem with the techniques in ",[353,2155,267],{"href":2156},"\u002Ftesting-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002Fdebugging-jest-axe-violations-in-ci\u002F",[550,2158],{},[373,2160,2162],{"id":2161},"how-to-verify","How to Verify",[324,2164,2165],{},"Run the suite locally and confirm it fails for the right reasons before trusting it:",[385,2167,2169],{"className":387,"code":2168,"language":389,"meta":390,"style":390},"npx jest SignupForm ConfirmDialog --runInBand\n",[328,2170,2171],{"__ignoreMap":390},[394,2172,2173,2176,2179,2181,2183],{"class":396,"line":397},[394,2174,2175],{"class":400},"npx",[394,2177,2178],{"class":404}," jest",[394,2180,592],{"class":404},[394,2182,1628],{"class":404},[394,2184,2185],{"class":408}," --runInBand\n",[324,2187,2188,2189,2192,2193,2195,2196,2198,2199,2201],{},"Now sabotage each component deliberately—delete the ",[328,2190,2191],{},"\u003Clabel htmlFor=\"email\">",", drop ",[328,2194,338],{},", or point ",[328,2197,1606],{}," at a missing id—and re-run. Each break must turn a test red with a readable message. A green suite after sabotage means your scope, your ",[328,2200,1243],{},", or your assertions are not actually exercising the contract.",[324,2203,2204,2205,2209,2210,2213],{},"Automated tests verify DOM state, not speech. Finish with a manual pass: open the form in a browser with ",[2206,2207,2208],"strong",{},"NVDA"," (Windows) or ",[2206,2211,2212],{},"VoiceOver"," (macOS), submit invalid data, and confirm the screen reader announces the error and reads it as the field's description when you focus the input. Open the modal and confirm it announces \"Delete project?, dialog\" and that focus is managed. The manual check catches what jsdom never can—real focus order, announcement timing, and contrast.",[550,2215],{},[373,2217,2219],{"id":2218},"common-a11y-mistakes","Common a11y Mistakes",[2221,2222,2223,2235,2241,2254,2266],"ul",{},[2224,2225,2226,2232,2233,693],"li",{},[2206,2227,2228,2229],{},"Unawaited ",[328,2230,2231],{},"axe()"," — the assertion runs before the promise resolves, so the test passes having checked nothing. Always ",[328,2234,544],{},[2224,2236,2237,2240],{},[2206,2238,2239],{},"Testing only the initial render"," — errors and dialogs appear after interaction. Drive the component into each state and run axe per state.",[2224,2242,2243,2250,2251,693],{},[2206,2244,2245,2246,2249],{},"Querying by ",[328,2247,2248],{},"data-testid"," instead of role\u002Fname"," — bypasses the accessibility tree, so naming regressions slip through. Prefer ",[328,2252,2253],{},"getByRole(..., { name })",[2224,2255,2256,2262,2263,2265],{},[2206,2257,2258,2259,2261],{},"Asserting ",[328,2260,345],{}," exists without checking it resolves"," — a dangling reference passes a presence check but breaks for AT. Use ",[328,2264,1558],{}," to confirm the text.",[2224,2267,2268,2271],{},[2206,2269,2270],{},"Trusting jest-axe for contrast or focus visibility"," — jsdom has no layout; push those to an end-to-end browser run.",[550,2273],{},[373,2275,2277],{"id":2276},"conclusion","Conclusion",[324,2279,2280,2281,2283],{},"With the matcher registered once, each component test becomes a render, an ",[328,2282,544],{},", and a small set of role\u002Fname assertions that pin down meaning. The form and modal here cover the two highest-risk patterns—error association and dialog semantics—and the same shape extends to menus, tabs, and toasts. Keep axe and semantic assertions paired, test every state, and confirm with a real screen reader.",[550,2285],{},[373,2287,2289],{"id":2288},"frequently-asked-questions","Frequently Asked Questions",[324,2291,2292,2302,2303,2306,2307,2309],{},[2206,2293,2294,2295,2298,2299,955],{},"Do I need ",[328,2296,2297],{},"userEvent"," or can I use ",[328,2300,2301],{},"fireEvent","\nUse ",[328,2304,2305],{},"@testing-library\u002Fuser-event",". It dispatches the full sequence of events a real user produces (focus, keydown, input, click), which more faithfully exercises the accessibility behavior—focus moving into a dialog, an input's invalid state updating—than the single synthetic event ",[328,2308,2301],{}," fires.",[324,2311,2312,2318,2319,2321,2322,2325,2326,2329,2330,693],{},[2206,2313,2314,2315,2317],{},"Why assert ",[328,2316,1558],{}," when axe already ran?","\nAxe confirms the ",[328,2320,345],{}," reference is structurally valid, but it does not know which message ",[348,2323,2324],{},"should"," describe the field. ",[328,2327,2328],{},"toHaveAccessibleDescription(\u002F...\u002F)"," verifies the field is actually described by the correct error text—the semantic contract behind ",[328,2331,370],{},[324,2333,2334,2340,2341,2343,2344,2346,2347,2349,2350,2353,2354,2357],{},[2206,2335,2336,2337,2339],{},"My dialog test can't find the dialog with ",[328,2338,1348],{},". What's wrong?","\nEither the dialog mounts asynchronously—use ",[328,2342,1434],{},", which waits—or it renders into a portal outside the ",[328,2345,1201],{},". ",[328,2348,1348],{}," searches the whole ",[328,2351,2352],{},"document"," by default, so a portal is fine for the query; the scope problem affects ",[328,2355,2356],{},"axe(container)",", not Testing Library queries.",[373,2359,2361,2364,2365,2367],{"id":2360},"should-i-run-axe-once-per-component-or-once-per-stateonce-per-meaningful-state-a-single-render-time-pass-misses-the-error-and-open-dialog-states-where-regressions-cluster-re-run-await-axecontainer-after-each-interaction-that-changes-the-dom",[2206,2362,2363],{},"Should I run axe once per component or once per state?","\nOnce per meaningful state. A single render-time pass misses the error and open-dialog states where regressions cluster. Re-run ",[328,2366,544],{}," after each interaction that changes the DOM.",[373,2369,2371],{"id":2370},"related-guides","Related guides",[2221,2373,2374,2378,2382],{},[2224,2375,2376],{},[353,2377,261],{"href":355},[2224,2379,2380],{},[353,2381,1587],{"href":1586},[2224,2383,2384],{},[353,2385,267],{"href":2156},[2387,2388,2389],"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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}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}",{"title":390,"searchDepth":421,"depth":421,"links":2391},[2392,2393,2394,2395,2396,2397,2398,2399,2401],{"id":375,"depth":421,"text":376},{"id":554,"depth":421,"text":555},{"id":1592,"depth":421,"text":1593},{"id":2161,"depth":421,"text":2162},{"id":2218,"depth":421,"text":2219},{"id":2276,"depth":421,"text":2277},{"id":2288,"depth":421,"text":2289},{"id":2360,"depth":421,"text":2400},"Should I run axe once per component or once per state?\nOnce per meaningful state. A single render-time pass misses the error and open-dialog states where regressions cluster. Re-run await axe(container) after each interaction that changes the DOM.",{"id":2370,"depth":421,"text":2371},null,"A step-by-step jest-axe walkthrough for React—set up the matcher, test an accessible form and modal for violations, and assert roles, names, and error associations.","md",{},false,{"title":273,"description":2403},"4ARiCe85xhP5KQEN4-dbhrb-pFyUKV7HidMtJFp_bBs",[2410,2449,2450,2513],{"title":5,"path":6,"stem":7,"children":2411},[2412,2413,2416,2419,2425,2431,2440,2446],{"title":10,"path":6,"stem":11},{"title":13,"path":14,"stem":15,"children":2414},[2415],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":2417},[2418],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":2420},[2421,2422],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":2423},[2424],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":2426},[2427,2428],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":2429},[2430],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":2432},[2433,2434,2437],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":2435},[2436],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":2438},[2439],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":2441},[2442,2443],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":2444},[2445],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":2447},[2448],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87},{"title":89,"path":90,"stem":91,"children":2451},[2452,2453,2459,2471,2483,2486,2495,2507],{"title":94,"path":90,"stem":95},{"title":97,"path":98,"stem":99,"children":2454},[2455,2456],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":2457},[2458],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":2460},[2461,2462,2465,2468],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":2463},[2464],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":2466},[2467],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":2469},[2470],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":2472},[2473,2474,2477,2480],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":2475},[2476],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":2478},[2479],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":2481},[2482],{"title":151,"path":152,"stem":153},{"title":157,"path":158,"stem":159,"children":2484},[2485],{"title":157,"path":158,"stem":159},{"title":163,"path":164,"stem":165,"children":2487},[2488,2489,2492],{"title":163,"path":164,"stem":165},{"title":169,"path":170,"stem":171,"children":2490},[2491],{"title":169,"path":170,"stem":171},{"title":175,"path":176,"stem":177,"children":2493},[2494],{"title":175,"path":176,"stem":177},{"title":181,"path":182,"stem":183,"children":2496},[2497,2498,2501,2504],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189,"children":2499},[2500],{"title":187,"path":188,"stem":189},{"title":193,"path":194,"stem":195,"children":2502},[2503],{"title":193,"path":194,"stem":195},{"title":199,"path":200,"stem":201,"children":2505},[2506],{"title":199,"path":200,"stem":201},{"title":205,"path":206,"stem":207,"children":2508},[2509,2510],{"title":205,"path":206,"stem":207},{"title":211,"path":212,"stem":213,"children":2511},[2512],{"title":211,"path":212,"stem":213},{"title":217,"path":218,"stem":219,"children":2514},[2515,2516,2525,2534,2543,2552],{"title":222,"path":218,"stem":223},{"title":225,"path":226,"stem":227,"children":2517},[2518,2519,2522],{"title":225,"path":226,"stem":227},{"title":231,"path":232,"stem":233,"children":2520},[2521],{"title":231,"path":232,"stem":233},{"title":237,"path":238,"stem":239,"children":2523},[2524],{"title":237,"path":238,"stem":239},{"title":243,"path":244,"stem":245,"children":2526},[2527,2528,2531],{"title":243,"path":244,"stem":245},{"title":249,"path":250,"stem":251,"children":2529},[2530],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257,"children":2532},[2533],{"title":255,"path":256,"stem":257},{"title":261,"path":262,"stem":263,"children":2535},[2536,2537,2540],{"title":261,"path":262,"stem":263},{"title":267,"path":268,"stem":269,"children":2538},[2539],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275,"children":2541},[2542],{"title":273,"path":274,"stem":275},{"title":279,"path":280,"stem":281,"children":2544},[2545,2546,2549],{"title":279,"path":280,"stem":281},{"title":285,"path":286,"stem":287,"children":2547},[2548],{"title":285,"path":286,"stem":287},{"title":291,"path":292,"stem":293,"children":2550},[2551],{"title":291,"path":292,"stem":293},{"title":297,"path":298,"stem":299,"children":2553},[2554,2555,2558],{"title":297,"path":298,"stem":299},{"title":303,"path":304,"stem":305,"children":2556},[2557],{"title":303,"path":304,"stem":305},{"title":309,"path":310,"stem":311,"children":2559},[2560],{"title":309,"path":310,"stem":311},1781785524237]