[{"data":1,"prerenderedAt":2272},["ShallowReactive",2],{"site-header-nav":3,"page-\u002Ftesting-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines\u002Ffailing-pull-requests-on-axe-violations\u002F":314,"content-navigation":2120},[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":309,"body":316,"date":2113,"description":2114,"extension":2115,"image":2113,"meta":2116,"modifiedAt":2113,"navigation":487,"noindex":2117,"path":310,"publishedAt":2113,"seo":2118,"stem":311,"updatedAt":2113,"__hash__":2119},"content\u002Ftesting-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines\u002Ffailing-pull-requests-on-axe-violations\u002Findex.md",{"type":317,"value":318,"toc":2102},"minimark",[319,323,337,343,363,368,387,390,395,398,413,619,626,715,728,730,734,741,920,927,1031,1041,1048,1050,1054,1066,1208,1257,1600,1618,1620,1624,1631,1692,1876,1886,1888,1892,1935,1937,1941,1944,1997,1999,2003,2006,2008,2012,2035,2044,2054,2064,2078,2080,2084,2098],[320,321,309],"h1",{"id":322},"failing-pull-requests-on-axe-violations",[324,325,326,327,331,332,336],"p",{},"A pull request should not merge while it ships a broken accessible name or an invalid ARIA role. This guide—part of ",[328,329,297],"a",{"href":330},"\u002Ftesting-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines\u002F","—covers the precise mechanics of turning an axe violation into a blocked merge: making the test command exit non-zero, wiring that job into a required status check, and printing the offending nodes onto the PR so the fix is obvious. The chain is mechanical and unforgiving: a violation of ",[333,334,335],"code",{},"4.1.2 Name, Role, Value"," produces exit code 1, the GitHub Actions job fails, the required check turns red, and branch protection refuses the merge.",[324,338,339],{},[340,341,342],"strong",{},"WCAG Coverage Mapping",[344,345,346,352,357],"ul",{},[347,348,349,351],"li",{},[333,350,335],{}," (Level A)",[347,353,354,351],{},[333,355,356],{},"1.3.1 Info and Relationships",[347,358,359,362],{},[333,360,361],{},"4.1.1 Parsing"," \u002F valid ARIA usage (Level A)",[324,364,365],{},[340,366,367],{},"Prerequisites",[344,369,370,377,384],{},[347,371,372,373,376],{},"A jest-axe or ",[333,374,375],{},"@axe-core\u002Fplaywright"," suite already asserting against your components or routes.",[347,378,379,380,383],{},"A GitHub Actions workflow that runs that suite on ",[333,381,382],{},"pull_request",".",[347,385,386],{},"Admin access to configure branch protection or a repository ruleset on the default branch.",[388,389],"hr",{},[391,392,394],"h2",{"id":393},"ensuring-the-test-command-exits-non-zero","Ensuring the Test Command Exits Non-Zero",[324,396,397],{},"The entire gate depends on one fact: the test process must return a non-zero exit code when an axe violation exists. A test runner does this automatically when an assertion fails—so the job is to make sure every violation triggers a failing assertion, and that nothing swallows the exit code afterward.",[324,399,400,401,404,405,408,409,412],{},"With ",[333,402,403],{},"jest-axe",", the ",[333,406,407],{},"toHaveNoViolations"," matcher fails the test the moment axe returns a non-empty ",[333,410,411],{},"violations"," array, and Jest exits 1 on any failed test:",[414,415,420],"pre",{"className":416,"code":417,"language":418,"meta":419,"style":419},"language-js shiki shiki-themes github-light github-dark","\u002F\u002F Button.a11y.test.jsx\nimport { render } from '@testing-library\u002Freact';\nimport { axe, toHaveNoViolations } from 'jest-axe';\nimport { IconButton } from '.\u002FIconButton';\n\nexpect.extend(toHaveNoViolations);\n\ntest('icon button exposes an accessible name', async () => {\n  const { container } = render(\u003CIconButton icon=\"close\" \u002F>);\n  const results = await axe(container);\n  \u002F\u002F Empty violations -> pass; any violation fails -> Jest exits non-zero.\n  expect(results).toHaveNoViolations();\n});\n","js","",[333,421,422,431,452,467,482,489,502,507,534,573,593,599,613],{"__ignoreMap":419},[423,424,427],"span",{"class":425,"line":426},"line",1,[423,428,430],{"class":429},"sJ8bj","\u002F\u002F Button.a11y.test.jsx\n",[423,432,434,438,442,445,449],{"class":425,"line":433},2,[423,435,437],{"class":436},"szBVR","import",[423,439,441],{"class":440},"sVt8B"," { render } ",[423,443,444],{"class":436},"from",[423,446,448],{"class":447},"sZZnC"," '@testing-library\u002Freact'",[423,450,451],{"class":440},";\n",[423,453,455,457,460,462,465],{"class":425,"line":454},3,[423,456,437],{"class":436},[423,458,459],{"class":440}," { axe, toHaveNoViolations } ",[423,461,444],{"class":436},[423,463,464],{"class":447}," 'jest-axe'",[423,466,451],{"class":440},[423,468,470,472,475,477,480],{"class":425,"line":469},4,[423,471,437],{"class":436},[423,473,474],{"class":440}," { IconButton } ",[423,476,444],{"class":436},[423,478,479],{"class":447}," '.\u002FIconButton'",[423,481,451],{"class":440},[423,483,485],{"class":425,"line":484},5,[423,486,488],{"emptyLinePlaceholder":487},true,"\n",[423,490,492,495,499],{"class":425,"line":491},6,[423,493,494],{"class":440},"expect.",[423,496,498],{"class":497},"sScJk","extend",[423,500,501],{"class":440},"(toHaveNoViolations);\n",[423,503,505],{"class":425,"line":504},7,[423,506,488],{"emptyLinePlaceholder":487},[423,508,510,513,516,519,522,525,528,531],{"class":425,"line":509},8,[423,511,512],{"class":497},"test",[423,514,515],{"class":440},"(",[423,517,518],{"class":447},"'icon button exposes an accessible name'",[423,520,521],{"class":440},", ",[423,523,524],{"class":436},"async",[423,526,527],{"class":440}," () ",[423,529,530],{"class":436},"=>",[423,532,533],{"class":440}," {\n",[423,535,537,540,543,547,550,553,556,559,562,565,567,570],{"class":425,"line":536},9,[423,538,539],{"class":436},"  const",[423,541,542],{"class":440}," { ",[423,544,546],{"class":545},"sj4cs","container",[423,548,549],{"class":440}," } ",[423,551,552],{"class":436},"=",[423,554,555],{"class":497}," render",[423,557,558],{"class":440},"(\u003C",[423,560,561],{"class":545},"IconButton",[423,563,564],{"class":497}," icon",[423,566,552],{"class":436},[423,568,569],{"class":447},"\"close\"",[423,571,572],{"class":440}," \u002F>);\n",[423,574,576,578,581,584,587,590],{"class":425,"line":575},10,[423,577,539],{"class":436},[423,579,580],{"class":545}," results",[423,582,583],{"class":436}," =",[423,585,586],{"class":436}," await",[423,588,589],{"class":497}," axe",[423,591,592],{"class":440},"(container);\n",[423,594,596],{"class":425,"line":595},11,[423,597,598],{"class":429},"  \u002F\u002F Empty violations -> pass; any violation fails -> Jest exits non-zero.\n",[423,600,602,605,608,610],{"class":425,"line":601},12,[423,603,604],{"class":497},"  expect",[423,606,607],{"class":440},"(results).",[423,609,407],{"class":497},[423,611,612],{"class":440},"();\n",[423,614,616],{"class":425,"line":615},13,[423,617,618],{"class":440},"});\n",[324,620,621,622,625],{},"The component rules behind this matcher are covered in ",[328,623,261],{"href":624},"\u002Ftesting-and-automating-accessibility\u002Fcomponent-testing-with-jest-axe\u002F",". The most common way teams accidentally break the gate is masking the exit code in the shell:",[414,627,631],{"className":628,"code":629,"language":630,"meta":419,"style":419},"language-bash shiki shiki-themes github-light github-dark","# WRONG — '|| true' swallows the failure; the job always reports success.\nnpm run test:a11y || true\n\n# WRONG — piping to tee drops the exit code to tee's (usually 0).\nnpm run test:a11y | tee a11y.log\n\n# RIGHT — preserve the runner's exit code through the pipe.\nset -o pipefail\nnpm run test:a11y | tee a11y.log\n","bash",[333,632,633,638,655,659,664,681,685,690,701],{"__ignoreMap":419},[423,634,635],{"class":425,"line":426},[423,636,637],{"class":429},"# WRONG — '|| true' swallows the failure; the job always reports success.\n",[423,639,640,643,646,649,652],{"class":425,"line":433},[423,641,642],{"class":497},"npm",[423,644,645],{"class":447}," run",[423,647,648],{"class":447}," test:a11y",[423,650,651],{"class":436}," ||",[423,653,654],{"class":545}," true\n",[423,656,657],{"class":425,"line":454},[423,658,488],{"emptyLinePlaceholder":487},[423,660,661],{"class":425,"line":469},[423,662,663],{"class":429},"# WRONG — piping to tee drops the exit code to tee's (usually 0).\n",[423,665,666,668,670,672,675,678],{"class":425,"line":484},[423,667,642],{"class":497},[423,669,645],{"class":447},[423,671,648],{"class":447},[423,673,674],{"class":436}," |",[423,676,677],{"class":497}," tee",[423,679,680],{"class":447}," a11y.log\n",[423,682,683],{"class":425,"line":491},[423,684,488],{"emptyLinePlaceholder":487},[423,686,687],{"class":425,"line":504},[423,688,689],{"class":429},"# RIGHT — preserve the runner's exit code through the pipe.\n",[423,691,692,695,698],{"class":425,"line":509},[423,693,694],{"class":545},"set",[423,696,697],{"class":545}," -o",[423,699,700],{"class":447}," pipefail\n",[423,702,703,705,707,709,711,713],{"class":425,"line":536},[423,704,642],{"class":497},[423,706,645],{"class":447},[423,708,648],{"class":447},[423,710,674],{"class":436},[423,712,677],{"class":497},[423,714,680],{"class":447},[716,717,718],"blockquote",{},[324,719,720,723,724,727],{},[340,721,722],{},"Gate Hook:"," After wiring the job, deliberately introduce one violation—remove an ",[333,725,726],{},"aria-label","—and confirm the job fails red. A gate you have never seen fail is a gate you cannot trust.",[388,729],{},[391,731,733],{"id":732},"the-actions-job-required-status-check-and-branch-protection","The Actions Job, Required Status Check, and Branch Protection",[324,735,736,737,740],{},"The job itself is small; its power comes from being marked required. Give the job a stable ",[333,738,739],{},"name"," or rely on its key—this string is what you reference in branch protection.",[414,742,746],{"className":743,"code":744,"language":745,"meta":419,"style":419},"language-yaml shiki shiki-themes github-light github-dark","# .github\u002Fworkflows\u002Fa11y.yml\nname: a11y\n\non:\n  pull_request:\n    branches: [main]\n\njobs:\n  axe:                          # \u003C-- this key is the required-check context\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: actions\u002Fsetup-node@v4\n        with: { node-version: 20, cache: npm }\n      - run: npm ci\n      # No '|| true', no bare pipe: a violation propagates exit code 1.\n      - run: npm run test:a11y -- --ci\n","yaml",[333,747,748,753,764,768,776,783,797,801,808,819,829,836,849,860,889,902,908],{"__ignoreMap":419},[423,749,750],{"class":425,"line":426},[423,751,752],{"class":429},"# .github\u002Fworkflows\u002Fa11y.yml\n",[423,754,755,758,761],{"class":425,"line":433},[423,756,739],{"class":757},"s9eBZ",[423,759,760],{"class":440},": ",[423,762,763],{"class":447},"a11y\n",[423,765,766],{"class":425,"line":454},[423,767,488],{"emptyLinePlaceholder":487},[423,769,770,773],{"class":425,"line":469},[423,771,772],{"class":545},"on",[423,774,775],{"class":440},":\n",[423,777,778,781],{"class":425,"line":484},[423,779,780],{"class":757},"  pull_request",[423,782,775],{"class":440},[423,784,785,788,791,794],{"class":425,"line":491},[423,786,787],{"class":757},"    branches",[423,789,790],{"class":440},": [",[423,792,793],{"class":447},"main",[423,795,796],{"class":440},"]\n",[423,798,799],{"class":425,"line":504},[423,800,488],{"emptyLinePlaceholder":487},[423,802,803,806],{"class":425,"line":509},[423,804,805],{"class":757},"jobs",[423,807,775],{"class":440},[423,809,810,813,816],{"class":425,"line":536},[423,811,812],{"class":757},"  axe",[423,814,815],{"class":440},":                          ",[423,817,818],{"class":429},"# \u003C-- this key is the required-check context\n",[423,820,821,824,826],{"class":425,"line":575},[423,822,823],{"class":757},"    runs-on",[423,825,760],{"class":440},[423,827,828],{"class":447},"ubuntu-latest\n",[423,830,831,834],{"class":425,"line":595},[423,832,833],{"class":757},"    steps",[423,835,775],{"class":440},[423,837,838,841,844,846],{"class":425,"line":601},[423,839,840],{"class":440},"      - ",[423,842,843],{"class":757},"uses",[423,845,760],{"class":440},[423,847,848],{"class":447},"actions\u002Fcheckout@v4\n",[423,850,851,853,855,857],{"class":425,"line":615},[423,852,840],{"class":440},[423,854,843],{"class":757},[423,856,760],{"class":440},[423,858,859],{"class":447},"actions\u002Fsetup-node@v4\n",[423,861,863,866,869,872,874,877,879,882,884,886],{"class":425,"line":862},14,[423,864,865],{"class":757},"        with",[423,867,868],{"class":440},": { ",[423,870,871],{"class":757},"node-version",[423,873,760],{"class":440},[423,875,876],{"class":545},"20",[423,878,521],{"class":440},[423,880,881],{"class":757},"cache",[423,883,760],{"class":440},[423,885,642],{"class":447},[423,887,888],{"class":440}," }\n",[423,890,892,894,897,899],{"class":425,"line":891},15,[423,893,840],{"class":440},[423,895,896],{"class":757},"run",[423,898,760],{"class":440},[423,900,901],{"class":447},"npm ci\n",[423,903,905],{"class":425,"line":904},16,[423,906,907],{"class":429},"      # No '|| true', no bare pipe: a violation propagates exit code 1.\n",[423,909,911,913,915,917],{"class":425,"line":910},17,[423,912,840],{"class":440},[423,914,896],{"class":757},[423,916,760],{"class":440},[423,918,919],{"class":447},"npm run test:a11y -- --ci\n",[324,921,922,923,926],{},"Now make the ",[333,924,925],{},"axe"," context required. Without this step the job can fail and the PR still merges—the red check is advisory until branch protection enforces it.",[414,928,930],{"className":743,"code":929,"language":745,"meta":419,"style":419},"# Repository ruleset on main (Settings → Rules → Rulesets)\ntarget: branch\nconditions:\n  ref_name: { include: [\"refs\u002Fheads\u002Fmain\"] }\nrules:\n  - type: required_status_checks\n    parameters:\n      strict_required_status_checks_policy: true\n      required_status_checks:\n        - context: axe            # exact job key from the workflow\n",[333,931,932,937,947,954,972,979,992,999,1009,1016],{"__ignoreMap":419},[423,933,934],{"class":425,"line":426},[423,935,936],{"class":429},"# Repository ruleset on main (Settings → Rules → Rulesets)\n",[423,938,939,942,944],{"class":425,"line":433},[423,940,941],{"class":757},"target",[423,943,760],{"class":440},[423,945,946],{"class":447},"branch\n",[423,948,949,952],{"class":425,"line":454},[423,950,951],{"class":757},"conditions",[423,953,775],{"class":440},[423,955,956,959,961,964,966,969],{"class":425,"line":469},[423,957,958],{"class":757},"  ref_name",[423,960,868],{"class":440},[423,962,963],{"class":757},"include",[423,965,790],{"class":440},[423,967,968],{"class":447},"\"refs\u002Fheads\u002Fmain\"",[423,970,971],{"class":440},"] }\n",[423,973,974,977],{"class":425,"line":484},[423,975,976],{"class":757},"rules",[423,978,775],{"class":440},[423,980,981,984,987,989],{"class":425,"line":491},[423,982,983],{"class":440},"  - ",[423,985,986],{"class":757},"type",[423,988,760],{"class":440},[423,990,991],{"class":447},"required_status_checks\n",[423,993,994,997],{"class":425,"line":504},[423,995,996],{"class":757},"    parameters",[423,998,775],{"class":440},[423,1000,1001,1004,1006],{"class":425,"line":509},[423,1002,1003],{"class":757},"      strict_required_status_checks_policy",[423,1005,760],{"class":440},[423,1007,1008],{"class":545},"true\n",[423,1010,1011,1014],{"class":425,"line":536},[423,1012,1013],{"class":757},"      required_status_checks",[423,1015,775],{"class":440},[423,1017,1018,1021,1024,1026,1028],{"class":425,"line":575},[423,1019,1020],{"class":440},"        - ",[423,1022,1023],{"class":757},"context",[423,1025,760],{"class":440},[423,1027,925],{"class":447},[423,1029,1030],{"class":429},"            # exact job key from the workflow\n",[324,1032,1033,1034,1037,1038,1040],{},"The ",[333,1035,1036],{},"strict"," policy forces the branch to be up to date with ",[333,1039,793],{}," before merging, so a violation cannot slip in through a stale base that was green before a conflicting change landed.",[716,1042,1043],{},[324,1044,1045,1047],{},[340,1046,722],{}," The context string must match the job key exactly, including case. A typo creates a required check that never reports, which silently blocks every PR. Verify it appears in the PR's check list after one run.",[388,1049],{},[391,1051,1053],{"id":1052},"printing-the-violation-summary-to-the-pr-and-log","Printing the Violation Summary to the PR and Log",[324,1055,1056,1057,1061,1062,1065],{},"A red check tells engineers ",[1058,1059,1060],"em",{},"that"," something failed; the summary tells them ",[1058,1063,1064],{},"what",". Serialize the axe results to JSON, then render a table into the GitHub step summary so reviewers see the rule and selector without opening raw logs.",[414,1067,1069],{"className":416,"code":1068,"language":418,"meta":419,"style":419},"\u002F\u002F jest reporter snippet — save results during the run\nimport fs from 'node:fs';\nimport { axe } from 'jest-axe';\n\nexport async function auditAndRecord(container, route) {\n  const results = await axe(container);\n  fs.appendFileSync('a11y-results.ndjson', JSON.stringify({ route, ...results }) + '\\n');\n  return results;\n}\n",[333,1070,1071,1076,1090,1103,1107,1134,1148,1195,1203],{"__ignoreMap":419},[423,1072,1073],{"class":425,"line":426},[423,1074,1075],{"class":429},"\u002F\u002F jest reporter snippet — save results during the run\n",[423,1077,1078,1080,1083,1085,1088],{"class":425,"line":433},[423,1079,437],{"class":436},[423,1081,1082],{"class":440}," fs ",[423,1084,444],{"class":436},[423,1086,1087],{"class":447}," 'node:fs'",[423,1089,451],{"class":440},[423,1091,1092,1094,1097,1099,1101],{"class":425,"line":454},[423,1093,437],{"class":436},[423,1095,1096],{"class":440}," { axe } ",[423,1098,444],{"class":436},[423,1100,464],{"class":447},[423,1102,451],{"class":440},[423,1104,1105],{"class":425,"line":469},[423,1106,488],{"emptyLinePlaceholder":487},[423,1108,1109,1112,1115,1118,1121,1123,1126,1128,1131],{"class":425,"line":484},[423,1110,1111],{"class":436},"export",[423,1113,1114],{"class":436}," async",[423,1116,1117],{"class":436}," function",[423,1119,1120],{"class":497}," auditAndRecord",[423,1122,515],{"class":440},[423,1124,546],{"class":1125},"s4XuR",[423,1127,521],{"class":440},[423,1129,1130],{"class":1125},"route",[423,1132,1133],{"class":440},") {\n",[423,1135,1136,1138,1140,1142,1144,1146],{"class":425,"line":491},[423,1137,539],{"class":436},[423,1139,580],{"class":545},[423,1141,583],{"class":436},[423,1143,586],{"class":436},[423,1145,589],{"class":497},[423,1147,592],{"class":440},[423,1149,1150,1153,1156,1158,1161,1163,1166,1168,1171,1174,1177,1180,1183,1186,1189,1192],{"class":425,"line":504},[423,1151,1152],{"class":440},"  fs.",[423,1154,1155],{"class":497},"appendFileSync",[423,1157,515],{"class":440},[423,1159,1160],{"class":447},"'a11y-results.ndjson'",[423,1162,521],{"class":440},[423,1164,1165],{"class":545},"JSON",[423,1167,383],{"class":440},[423,1169,1170],{"class":497},"stringify",[423,1172,1173],{"class":440},"({ route, ",[423,1175,1176],{"class":436},"...",[423,1178,1179],{"class":440},"results }) ",[423,1181,1182],{"class":436},"+",[423,1184,1185],{"class":447}," '",[423,1187,1188],{"class":545},"\\n",[423,1190,1191],{"class":447},"'",[423,1193,1194],{"class":440},");\n",[423,1196,1197,1200],{"class":425,"line":509},[423,1198,1199],{"class":436},"  return",[423,1201,1202],{"class":440}," results;\n",[423,1204,1205],{"class":425,"line":536},[423,1206,1207],{"class":440},"}\n",[414,1209,1211],{"className":743,"code":1210,"language":745,"meta":419,"style":419},"  - name: Publish axe summary to the PR\n    if: always()                # run even after the test step fails\n    run: |\n      set -o pipefail\n      node .\u002Fscripts\u002Faxe-summary.js >> \"$GITHUB_STEP_SUMMARY\"\n",[333,1212,1213,1224,1237,1247,1252],{"__ignoreMap":419},[423,1214,1215,1217,1219,1221],{"class":425,"line":426},[423,1216,983],{"class":440},[423,1218,739],{"class":757},[423,1220,760],{"class":440},[423,1222,1223],{"class":447},"Publish axe summary to the PR\n",[423,1225,1226,1229,1231,1234],{"class":425,"line":433},[423,1227,1228],{"class":757},"    if",[423,1230,760],{"class":440},[423,1232,1233],{"class":447},"always()",[423,1235,1236],{"class":429},"                # run even after the test step fails\n",[423,1238,1239,1242,1244],{"class":425,"line":454},[423,1240,1241],{"class":757},"    run",[423,1243,760],{"class":440},[423,1245,1246],{"class":436},"|\n",[423,1248,1249],{"class":425,"line":469},[423,1250,1251],{"class":447},"      set -o pipefail\n",[423,1253,1254],{"class":425,"line":484},[423,1255,1256],{"class":447},"      node .\u002Fscripts\u002Faxe-summary.js >> \"$GITHUB_STEP_SUMMARY\"\n",[414,1258,1260],{"className":416,"code":1259,"language":418,"meta":419,"style":419},"\u002F\u002F scripts\u002Faxe-summary.js\nconst fs = require('node:fs');\nconst lines = fs.readFileSync('a11y-results.ndjson', 'utf8').trim().split('\\n');\nconsole.log('### Accessibility violations\\n');\nconsole.log('| Route | Rule | Impact | Selector |');\nconsole.log('| --- | --- | --- | --- |');\nlet count = 0;\nfor (const line of lines) {\n  const { route, violations } = JSON.parse(line);\n  for (const v of violations) {\n    for (const node of v.nodes) {\n      count++;\n      console.log(`| ${route} | ${v.id} | ${v.impact} | \\`${node.target.join(' ')}\\` |`);\n    }\n  }\n}\n\u002F\u002F Mirror the gate state so this step is also red when violations exist.\nprocess.exit(count ? 1 : 0);\n",[333,1261,1262,1267,1287,1333,1352,1365,1378,1393,1412,1439,1456,1473,1483,1556,1561,1566,1570,1575],{"__ignoreMap":419},[423,1263,1264],{"class":425,"line":426},[423,1265,1266],{"class":429},"\u002F\u002F scripts\u002Faxe-summary.js\n",[423,1268,1269,1272,1275,1277,1280,1282,1285],{"class":425,"line":433},[423,1270,1271],{"class":436},"const",[423,1273,1274],{"class":545}," fs",[423,1276,583],{"class":436},[423,1278,1279],{"class":497}," require",[423,1281,515],{"class":440},[423,1283,1284],{"class":447},"'node:fs'",[423,1286,1194],{"class":440},[423,1288,1289,1291,1294,1296,1299,1302,1304,1306,1308,1311,1314,1317,1320,1323,1325,1327,1329,1331],{"class":425,"line":454},[423,1290,1271],{"class":436},[423,1292,1293],{"class":545}," lines",[423,1295,583],{"class":436},[423,1297,1298],{"class":440}," fs.",[423,1300,1301],{"class":497},"readFileSync",[423,1303,515],{"class":440},[423,1305,1160],{"class":447},[423,1307,521],{"class":440},[423,1309,1310],{"class":447},"'utf8'",[423,1312,1313],{"class":440},").",[423,1315,1316],{"class":497},"trim",[423,1318,1319],{"class":440},"().",[423,1321,1322],{"class":497},"split",[423,1324,515],{"class":440},[423,1326,1191],{"class":447},[423,1328,1188],{"class":545},[423,1330,1191],{"class":447},[423,1332,1194],{"class":440},[423,1334,1335,1338,1341,1343,1346,1348,1350],{"class":425,"line":469},[423,1336,1337],{"class":440},"console.",[423,1339,1340],{"class":497},"log",[423,1342,515],{"class":440},[423,1344,1345],{"class":447},"'### Accessibility violations",[423,1347,1188],{"class":545},[423,1349,1191],{"class":447},[423,1351,1194],{"class":440},[423,1353,1354,1356,1358,1360,1363],{"class":425,"line":484},[423,1355,1337],{"class":440},[423,1357,1340],{"class":497},[423,1359,515],{"class":440},[423,1361,1362],{"class":447},"'| Route | Rule | Impact | Selector |'",[423,1364,1194],{"class":440},[423,1366,1367,1369,1371,1373,1376],{"class":425,"line":491},[423,1368,1337],{"class":440},[423,1370,1340],{"class":497},[423,1372,515],{"class":440},[423,1374,1375],{"class":447},"'| --- | --- | --- | --- |'",[423,1377,1194],{"class":440},[423,1379,1380,1383,1386,1388,1391],{"class":425,"line":504},[423,1381,1382],{"class":436},"let",[423,1384,1385],{"class":440}," count ",[423,1387,552],{"class":436},[423,1389,1390],{"class":545}," 0",[423,1392,451],{"class":440},[423,1394,1395,1398,1401,1403,1406,1409],{"class":425,"line":509},[423,1396,1397],{"class":436},"for",[423,1399,1400],{"class":440}," (",[423,1402,1271],{"class":436},[423,1404,1405],{"class":545}," line",[423,1407,1408],{"class":436}," of",[423,1410,1411],{"class":440}," lines) {\n",[423,1413,1414,1416,1418,1420,1422,1424,1426,1428,1431,1433,1436],{"class":425,"line":536},[423,1415,539],{"class":436},[423,1417,542],{"class":440},[423,1419,1130],{"class":545},[423,1421,521],{"class":440},[423,1423,411],{"class":545},[423,1425,549],{"class":440},[423,1427,552],{"class":436},[423,1429,1430],{"class":545}," JSON",[423,1432,383],{"class":440},[423,1434,1435],{"class":497},"parse",[423,1437,1438],{"class":440},"(line);\n",[423,1440,1441,1444,1446,1448,1451,1453],{"class":425,"line":575},[423,1442,1443],{"class":436},"  for",[423,1445,1400],{"class":440},[423,1447,1271],{"class":436},[423,1449,1450],{"class":545}," v",[423,1452,1408],{"class":436},[423,1454,1455],{"class":440}," violations) {\n",[423,1457,1458,1461,1463,1465,1468,1470],{"class":425,"line":595},[423,1459,1460],{"class":436},"    for",[423,1462,1400],{"class":440},[423,1464,1271],{"class":436},[423,1466,1467],{"class":545}," node",[423,1469,1408],{"class":436},[423,1471,1472],{"class":440}," v.nodes) {\n",[423,1474,1475,1478,1481],{"class":425,"line":601},[423,1476,1477],{"class":440},"      count",[423,1479,1480],{"class":436},"++",[423,1482,451],{"class":440},[423,1484,1485,1488,1490,1492,1495,1497,1500,1503,1505,1508,1510,1512,1514,1517,1520,1523,1526,1529,1531,1533,1535,1538,1540,1543,1546,1549,1551,1554],{"class":425,"line":615},[423,1486,1487],{"class":440},"      console.",[423,1489,1340],{"class":497},[423,1491,515],{"class":440},[423,1493,1494],{"class":447},"`| ${",[423,1496,1130],{"class":440},[423,1498,1499],{"class":447},"} | ${",[423,1501,1502],{"class":440},"v",[423,1504,383],{"class":447},[423,1506,1507],{"class":440},"id",[423,1509,1499],{"class":447},[423,1511,1502],{"class":440},[423,1513,383],{"class":447},[423,1515,1516],{"class":440},"impact",[423,1518,1519],{"class":447},"} | ",[423,1521,1522],{"class":545},"\\`",[423,1524,1525],{"class":447},"${",[423,1527,1528],{"class":440},"node",[423,1530,383],{"class":447},[423,1532,941],{"class":440},[423,1534,383],{"class":447},[423,1536,1537],{"class":497},"join",[423,1539,515],{"class":447},[423,1541,1542],{"class":447},"' '",[423,1544,1545],{"class":447},")",[423,1547,1548],{"class":447},"}",[423,1550,1522],{"class":545},[423,1552,1553],{"class":447}," |`",[423,1555,1194],{"class":440},[423,1557,1558],{"class":425,"line":862},[423,1559,1560],{"class":440},"    }\n",[423,1562,1563],{"class":425,"line":891},[423,1564,1565],{"class":440},"  }\n",[423,1567,1568],{"class":425,"line":904},[423,1569,1207],{"class":440},[423,1571,1572],{"class":425,"line":910},[423,1573,1574],{"class":429},"\u002F\u002F Mirror the gate state so this step is also red when violations exist.\n",[423,1576,1578,1581,1584,1587,1590,1593,1596,1598],{"class":425,"line":1577},18,[423,1579,1580],{"class":440},"process.",[423,1582,1583],{"class":497},"exit",[423,1585,1586],{"class":440},"(count ",[423,1588,1589],{"class":436},"?",[423,1591,1592],{"class":545}," 1",[423,1594,1595],{"class":436}," :",[423,1597,1390],{"class":545},[423,1599,1194],{"class":440},[324,1601,1602,1603,1606,1607,1610,1611,1614,1615,1617],{},"This renders a Markdown panel on the run showing, for example, ",[333,1604,1605],{},"button-name"," with impact ",[333,1608,1609],{},"serious"," on selector ",[333,1612,1613],{},".toolbar > button:nth-child(2)","—enough to locate and fix the ",[333,1616,335],{}," defect immediately.",[388,1619],{},[391,1621,1623],{"id":1622},"quarantining-known-issues-without-hiding-new-ones","Quarantining Known Issues Without Hiding New Ones",[324,1625,1626,1627,1630],{},"When a gate fails on pre-existing debt, the temptation is to disable a rule globally—which also hides every future instance of that rule. Instead, quarantine by ",[340,1628,1629],{},"rule plus selector"," so a brand-new occurrence of the same rule elsewhere still fails.",[414,1632,1634],{"className":416,"code":1633,"language":418,"meta":419,"style":419},"\u002F\u002F a11y-quarantine.js — narrowly scoped, triaged debt\nmodule.exports = [\n  { rule: 'color-contrast', selector: '.legacy-footer a' },\n  { rule: 'label', selector: '#archived-search-input' },\n];\n",[333,1635,1636,1641,1656,1673,1687],{"__ignoreMap":419},[423,1637,1638],{"class":425,"line":426},[423,1639,1640],{"class":429},"\u002F\u002F a11y-quarantine.js — narrowly scoped, triaged debt\n",[423,1642,1643,1646,1648,1651,1653],{"class":425,"line":433},[423,1644,1645],{"class":545},"module",[423,1647,383],{"class":440},[423,1649,1650],{"class":545},"exports",[423,1652,583],{"class":436},[423,1654,1655],{"class":440}," [\n",[423,1657,1658,1661,1664,1667,1670],{"class":425,"line":454},[423,1659,1660],{"class":440},"  { rule: ",[423,1662,1663],{"class":447},"'color-contrast'",[423,1665,1666],{"class":440},", selector: ",[423,1668,1669],{"class":447},"'.legacy-footer a'",[423,1671,1672],{"class":440}," },\n",[423,1674,1675,1677,1680,1682,1685],{"class":425,"line":469},[423,1676,1660],{"class":440},[423,1678,1679],{"class":447},"'label'",[423,1681,1666],{"class":440},[423,1683,1684],{"class":447},"'#archived-search-input'",[423,1686,1672],{"class":440},[423,1688,1689],{"class":425,"line":484},[423,1690,1691],{"class":440},"];\n",[414,1693,1695],{"className":416,"code":1694,"language":418,"meta":419,"style":419},"\u002F\u002F subtract quarantine, then assert only on new violations\nimport quarantine from '.\u002Fa11y-quarantine';\n\nexport function expectNoNewViolations(results) {\n  const isQuarantined = (v) =>\n    v.nodes.every((n) =>\n      quarantine.some((q) => q.rule === v.id && n.target.join(' ').includes(q.selector)),\n    );\n  const fresh = results.violations.filter((v) => !isQuarantined(v));\n  expect(fresh).toEqual([]); \u002F\u002F new debt fails; quarantined debt is tracked\n}\n",[333,1696,1697,1702,1716,1720,1736,1755,1773,1819,1824,1856,1872],{"__ignoreMap":419},[423,1698,1699],{"class":425,"line":426},[423,1700,1701],{"class":429},"\u002F\u002F subtract quarantine, then assert only on new violations\n",[423,1703,1704,1706,1709,1711,1714],{"class":425,"line":433},[423,1705,437],{"class":436},[423,1707,1708],{"class":440}," quarantine ",[423,1710,444],{"class":436},[423,1712,1713],{"class":447}," '.\u002Fa11y-quarantine'",[423,1715,451],{"class":440},[423,1717,1718],{"class":425,"line":454},[423,1719,488],{"emptyLinePlaceholder":487},[423,1721,1722,1724,1726,1729,1731,1734],{"class":425,"line":469},[423,1723,1111],{"class":436},[423,1725,1117],{"class":436},[423,1727,1728],{"class":497}," expectNoNewViolations",[423,1730,515],{"class":440},[423,1732,1733],{"class":1125},"results",[423,1735,1133],{"class":440},[423,1737,1738,1740,1743,1745,1747,1749,1752],{"class":425,"line":484},[423,1739,539],{"class":436},[423,1741,1742],{"class":497}," isQuarantined",[423,1744,583],{"class":436},[423,1746,1400],{"class":440},[423,1748,1502],{"class":1125},[423,1750,1751],{"class":440},") ",[423,1753,1754],{"class":436},"=>\n",[423,1756,1757,1760,1763,1766,1769,1771],{"class":425,"line":491},[423,1758,1759],{"class":440},"    v.nodes.",[423,1761,1762],{"class":497},"every",[423,1764,1765],{"class":440},"((",[423,1767,1768],{"class":1125},"n",[423,1770,1751],{"class":440},[423,1772,1754],{"class":436},[423,1774,1775,1778,1781,1783,1786,1788,1790,1793,1796,1799,1802,1805,1807,1809,1811,1813,1816],{"class":425,"line":504},[423,1776,1777],{"class":440},"      quarantine.",[423,1779,1780],{"class":497},"some",[423,1782,1765],{"class":440},[423,1784,1785],{"class":1125},"q",[423,1787,1751],{"class":440},[423,1789,530],{"class":436},[423,1791,1792],{"class":440}," q.rule ",[423,1794,1795],{"class":436},"===",[423,1797,1798],{"class":440}," v.id ",[423,1800,1801],{"class":436},"&&",[423,1803,1804],{"class":440}," n.target.",[423,1806,1537],{"class":497},[423,1808,515],{"class":440},[423,1810,1542],{"class":447},[423,1812,1313],{"class":440},[423,1814,1815],{"class":497},"includes",[423,1817,1818],{"class":440},"(q.selector)),\n",[423,1820,1821],{"class":425,"line":509},[423,1822,1823],{"class":440},"    );\n",[423,1825,1826,1828,1831,1833,1836,1839,1841,1843,1845,1847,1850,1853],{"class":425,"line":536},[423,1827,539],{"class":436},[423,1829,1830],{"class":545}," fresh",[423,1832,583],{"class":436},[423,1834,1835],{"class":440}," results.violations.",[423,1837,1838],{"class":497},"filter",[423,1840,1765],{"class":440},[423,1842,1502],{"class":1125},[423,1844,1751],{"class":440},[423,1846,530],{"class":436},[423,1848,1849],{"class":436}," !",[423,1851,1852],{"class":497},"isQuarantined",[423,1854,1855],{"class":440},"(v));\n",[423,1857,1858,1860,1863,1866,1869],{"class":425,"line":575},[423,1859,604],{"class":497},[423,1861,1862],{"class":440},"(fresh).",[423,1864,1865],{"class":497},"toEqual",[423,1867,1868],{"class":440},"([]); ",[423,1870,1871],{"class":429},"\u002F\u002F new debt fails; quarantined debt is tracked\n",[423,1873,1874],{"class":425,"line":595},[423,1875,1207],{"class":440},[324,1877,1878,1879,1882,1883,383],{},"Never quarantine an entire rule (",[333,1880,1881],{},"disableRules: ['color-contrast']",")—that blinds the gate to new contrast failures across the whole app. The diff-against-baseline approach that scales this to a full codebase is covered in ",[328,1884,303],{"href":1885},"\u002Ftesting-and-automating-accessibility\u002Fgating-accessibility-in-ci-cd-pipelines\u002Faccessibility-regression-testing-in-github-actions\u002F",[388,1887],{},[391,1889,1891],{"id":1890},"common-a11y-mistakes","Common a11y Mistakes",[344,1893,1894,1908,1917,1923,1929],{},[347,1895,1896,1899,1900,1903,1904,1907],{},[340,1897,1898],{},"Swallowing the exit code"," with ",[333,1901,1902],{},"|| true",", a bare pipe, or ",[333,1905,1906],{},"continue-on-error: true","—the job reports green while violations ship.",[347,1909,1910,1916],{},[340,1911,1912,1913],{},"Forgetting ",[333,1914,1915],{},"expect.extend(toHaveNoViolations)",", so the matcher is undefined and the test errors out in a way that gets mistaken for an unrelated failure.",[347,1918,1919,1922],{},[340,1920,1921],{},"Marking the workflow required instead of the job",", or mistyping the context, creating a check that never reports.",[347,1924,1925,1928],{},[340,1926,1927],{},"Disabling a rule globally"," to clear legacy debt, hiding every future instance instead of quarantining one selector.",[347,1930,1931,1934],{},[340,1932,1933],{},"Skipping the workflow on a path filter",", so the required check never runs and the PR blocks indefinitely.",[388,1936],{},[391,1938,1940],{"id":1939},"how-to-verify","How to Verify",[324,1942,1943],{},"Confirm the gate actually blocks a merge—do not assume it works because it is green.",[1945,1946,1947,1959,1968,1981,1991],"ol",{},[347,1948,1949,1952,1953,1955,1956,1958],{},[340,1950,1951],{},"Force a failure (tool check):"," Remove an ",[333,1954,726],{}," from one icon button, push to a branch, and open a PR. The ",[333,1957,925],{}," job must fail and the merge button must be disabled with \"Required check failing.\"",[347,1960,1961,1964,1965,1967],{},[340,1962,1963],{},"Inspect the summary (manual check):"," Open the failed run and confirm the step summary lists the rule (",[333,1966,1605],{},"), impact, and selector for the broken node.",[347,1969,1970,1973,1974,1976,1977,1980],{},[340,1971,1972],{},"Confirm the required check (manual check):"," In the PR's checks list, verify ",[333,1975,925],{}," is labeled ",[340,1978,1979],{},"Required",". If it is only \"Expected,\" the branch protection context string does not match the job key.",[347,1982,1983,1986,1987,1990],{},[340,1984,1985],{},"Confirm quarantine scoping (tool check):"," Add a ",[1058,1988,1989],{},"new"," instance of a quarantined rule on a fresh element and confirm the job still fails—proving the quarantine is selector-scoped, not rule-wide.",[347,1992,1993,1996],{},[340,1994,1995],{},"Restore and re-run:"," Re-add the label, push, and confirm the check returns green and the merge unblocks.",[388,1998],{},[391,2000,2002],{"id":2001},"conclusion","Conclusion",[324,2004,2005],{},"Failing a PR on axe violations is a three-link chain: a non-zero exit code, a required status check, and branch protection that honors it. Break any link—swallow the exit, mistype the context, leave the check advisory—and violations ship silently. Get all three right, scope your quarantine narrowly, and surface the failing nodes on the PR, and accessibility regressions stop at the pull request instead of in production.",[388,2007],{},[391,2009,2011],{"id":2010},"frequently-asked-questions","Frequently Asked Questions",[324,2013,2014,2017,2018,2020,2021,2024,2025,2028,2029,2031,2032,2034],{},[340,2015,2016],{},"Why does my axe job pass even though there are violations?","\nAlmost always the exit code is being swallowed—check for ",[333,2019,1902],{},", a bare ",[333,2022,2023],{},"| tee"," without ",[333,2026,2027],{},"set -o pipefail",", or ",[333,2030,1906],{}," on the step. Also confirm you called ",[333,2033,1915],{},"; without it the matcher is undefined and the assertion never fails as intended.",[324,2036,2037,2040,2041,2043],{},[340,2038,2039],{},"The check is red but the PR still merges—why?","\nThe check is not actually required. A red status is advisory until you add its exact job-key context to branch protection or a repository ruleset on the default branch. Verify the context appears as ",[340,2042,1979],{},", not merely \"Expected,\" in the PR's checks list.",[324,2045,2046,2049,2050,2053],{},[340,2047,2048],{},"How do I clear existing violations without disabling the rule everywhere?","\nQuarantine by rule ",[1058,2051,2052],{},"and"," selector rather than disabling the rule globally. A scoped allowlist suppresses the one known instance while still failing on any new occurrence of the same rule elsewhere in the app.",[324,2055,2056,2059,2060,2063],{},[340,2057,2058],{},"Where do reviewers see which element failed?","\nWrite the axe results to ",[333,2061,2062],{},"$GITHUB_STEP_SUMMARY"," as a Markdown table of route, rule, impact, and CSS selector. That renders a panel on the run, so the failing node is visible without opening raw logs, and pairs well with an uploaded HTML report.",[324,2065,2066,2069,2070,2073,2074,2077],{},[340,2067,2068],{},"Should the gate fail on best-practice rules or only WCAG rules?","\nGate on ",[333,2071,2072],{},"wcag2a"," and ",[333,2075,2076],{},"wcag2aa"," tags for a stable conformance target. Best-practice rules are useful as warnings but change between axe-core minor versions, which can turn a previously green build red on an unrelated dependency bump.",[388,2079],{},[391,2081,2083],{"id":2082},"related-guides","Related guides",[344,2085,2086,2090,2094],{},[347,2087,2088],{},[328,2089,297],{"href":330},[347,2091,2092],{},[328,2093,261],{"href":624},[347,2095,2096],{},[328,2097,303],{"href":1885},[2099,2100,2101],"style",{},"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 .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":419,"searchDepth":433,"depth":433,"links":2103},[2104,2105,2106,2107,2108,2109,2110,2111,2112],{"id":393,"depth":433,"text":394},{"id":732,"depth":433,"text":733},{"id":1052,"depth":433,"text":1053},{"id":1622,"depth":433,"text":1623},{"id":1890,"depth":433,"text":1891},{"id":1939,"depth":433,"text":1940},{"id":2001,"depth":433,"text":2002},{"id":2010,"depth":433,"text":2011},{"id":2082,"depth":433,"text":2083},null,"Block merges when accessibility breaks—make axe and jest-axe return non-zero exit codes, mark the job as a required status check, and surface the failing nodes in the PR.","md",{},false,{"title":309,"description":2114},"uJ9yS6rcGQTG_aeVfuXndV3O_gLrg-nv_QTzf2xJ8Bk",[2121,2160,2161,2224],{"title":5,"path":6,"stem":7,"children":2122},[2123,2124,2127,2130,2136,2142,2151,2157],{"title":10,"path":6,"stem":11},{"title":13,"path":14,"stem":15,"children":2125},[2126],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":2128},[2129],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":2131},[2132,2133],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":2134},[2135],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":2137},[2138,2139],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":2140},[2141],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":2143},[2144,2145,2148],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":2146},[2147],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":2149},[2150],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":2152},[2153,2154],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":2155},[2156],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":2158},[2159],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87},{"title":89,"path":90,"stem":91,"children":2162},[2163,2164,2170,2182,2194,2197,2206,2218],{"title":94,"path":90,"stem":95},{"title":97,"path":98,"stem":99,"children":2165},[2166,2167],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":2168},[2169],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":2171},[2172,2173,2176,2179],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":2174},[2175],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":2177},[2178],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":2180},[2181],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":2183},[2184,2185,2188,2191],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":2186},[2187],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":2189},[2190],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":2192},[2193],{"title":151,"path":152,"stem":153},{"title":157,"path":158,"stem":159,"children":2195},[2196],{"title":157,"path":158,"stem":159},{"title":163,"path":164,"stem":165,"children":2198},[2199,2200,2203],{"title":163,"path":164,"stem":165},{"title":169,"path":170,"stem":171,"children":2201},[2202],{"title":169,"path":170,"stem":171},{"title":175,"path":176,"stem":177,"children":2204},[2205],{"title":175,"path":176,"stem":177},{"title":181,"path":182,"stem":183,"children":2207},[2208,2209,2212,2215],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189,"children":2210},[2211],{"title":187,"path":188,"stem":189},{"title":193,"path":194,"stem":195,"children":2213},[2214],{"title":193,"path":194,"stem":195},{"title":199,"path":200,"stem":201,"children":2216},[2217],{"title":199,"path":200,"stem":201},{"title":205,"path":206,"stem":207,"children":2219},[2220,2221],{"title":205,"path":206,"stem":207},{"title":211,"path":212,"stem":213,"children":2222},[2223],{"title":211,"path":212,"stem":213},{"title":217,"path":218,"stem":219,"children":2225},[2226,2227,2236,2245,2254,2263],{"title":222,"path":218,"stem":223},{"title":225,"path":226,"stem":227,"children":2228},[2229,2230,2233],{"title":225,"path":226,"stem":227},{"title":231,"path":232,"stem":233,"children":2231},[2232],{"title":231,"path":232,"stem":233},{"title":237,"path":238,"stem":239,"children":2234},[2235],{"title":237,"path":238,"stem":239},{"title":243,"path":244,"stem":245,"children":2237},[2238,2239,2242],{"title":243,"path":244,"stem":245},{"title":249,"path":250,"stem":251,"children":2240},[2241],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257,"children":2243},[2244],{"title":255,"path":256,"stem":257},{"title":261,"path":262,"stem":263,"children":2246},[2247,2248,2251],{"title":261,"path":262,"stem":263},{"title":267,"path":268,"stem":269,"children":2249},[2250],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275,"children":2252},[2253],{"title":273,"path":274,"stem":275},{"title":279,"path":280,"stem":281,"children":2255},[2256,2257,2260],{"title":279,"path":280,"stem":281},{"title":285,"path":286,"stem":287,"children":2258},[2259],{"title":285,"path":286,"stem":287},{"title":291,"path":292,"stem":293,"children":2261},[2262],{"title":291,"path":292,"stem":293},{"title":297,"path":298,"stem":299,"children":2264},[2265,2266,2269],{"title":297,"path":298,"stem":299},{"title":303,"path":304,"stem":305,"children":2267},[2268],{"title":303,"path":304,"stem":305},{"title":309,"path":310,"stem":311,"children":2270},[2271],{"title":309,"path":310,"stem":311},1781785524264]