[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F2AHCPQB0PSXV6H5AF7PP6":3,"skill-same:01M6F2AHCPQB0PSXV6H5AF7PP6":84},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":29,"publishedAt":50,"linkOnly":51,"markdownContent":52,"markdownBody":53,"frontMatter":54,"refName":61,"commitSha":62,"skillDirectory":63,"forksCount":64,"repositoryHtmlUrl":65,"repositoryHomepageUrl":66,"ownerHtmlUrl":67,"ownerBlogUrl":6,"ownerDescription":68,"updatedAt":69,"install":70,"resources":83},"01M6F2AHCPQB0PSXV6H5AF7PP6","a11y-audit",null,"Accessibility audit skill for scanning, fixing, and verifying WCAG 2.2 Level A and AA compliance across React, Next.js, Vue, Angular, Svelte, and plain HTML codebases. Use when auditing accessibility, fixing a11y violations, checking color contrast, generating compliance reports, or integrating accessibility checks into CI\u002FCD pipelines.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",66,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Falirezarezvani\u002Fclaude-skills\u002Fa11y-audit\u002FSKILL.md",[18,23,26],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000011","运维与部署",{"categoryNo":27,"categoryName":28,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49],"audit","a11y audit","CI\u002FCD","react","svelte","across","next","wed","Generating Compliance Reports","reports","pipelines","scanning","use","js","Next.js","WCAG","angular","engineering","ci","vue","2026-11-10 10:12:30",false,"---\nName: a11y-audit\nTier: STANDARD\nCategory: engineering\nDependencies: none\nAuthor: Alireza Rezvani\nVersion: 2.1.2\nname: \"a11y-audit\"\ndescription: \"Accessibility audit skill for scanning, fixing, and verifying WCAG 2.2 Level A and AA compliance across React, Next.js, Vue, Angular, Svelte, and plain HTML codebases. Use when auditing accessibility, fixing a11y violations, checking color contrast, generating compliance reports, or integrating accessibility checks into CI\u002FCD pipelines.\"\nlicense: MIT\nmetadata:\n  updated: 2026-03-18\n---\n\n# Accessibility Audit\n\n---\n\n**Name**: a11y-audit\n**Tier**: STANDARD\n**Category**: Engineering - Frontend Quality\n**Dependencies**: Python 3.8+ (Standard Library Only)\n**Author**: Alireza Rezvani\n**Version**: 2.1.2\n**Last Updated**: 2026-03-18\n**License**: MIT\n\n---\n\n## Name\n\na11y-audit -- WCAG 2.2 Accessibility Audit and Remediation Skill\n\n## Description\n\nThe a11y-audit skill provides a complete accessibility audit pipeline for modern web applications. It implements a three-phase workflow -- Scan, Fix, Verify -- that identifies WCAG 2.2 Level A and AA violations, generates exact fix code per framework, and produces stakeholder-ready compliance reports.\n\nThis skill goes beyond detection. For every violation it finds, it provides the precise before\u002Fafter code fix tailored to your framework (React, Next.js, Vue, Angular, Svelte, or plain HTML). It understands that a missing `alt` attribute on an `\u003Cimg>` in React JSX requires a different fix pattern than the same issue in a Vue SFC or an Angular template.\n\n**What this skill does:**\n\n1. **Scans** your codebase for every WCAG 2.2 Level A and AA violation, categorized by severity (Critical, Major, Minor)\n2. **Fixes** each violation with framework-specific before\u002Fafter code patterns\n3. **Verifies** that fixes resolve the original violations and introduces no regressions\n4. **Reports** findings in a structured format suitable for developers, PMs, and compliance stakeholders\n5. **Integrates** into CI\u002FCD pipelines to prevent accessibility regressions\n\n**Key differentiators:**\n\n- Framework-aware fix patterns (not generic HTML advice)\n- Color contrast analysis with accessible alternative suggestions\n- WCAG 2.2 coverage including the newest success criteria (Focus Appearance, Dragging Movements, Target Size)\n- CI\u002FCD pipeline integration with GitHub Actions, GitLab CI, and Azure DevOps\n- Slash command support via `\u002Fa11y-audit`\n\n## Features\n\n### Core Capabilities\n\n| Feature | Description |\n|---------|-------------|\n| **Full WCAG 2.2 Scan** | Checks all Level A and AA success criteria across your codebase |\n| **Framework Detection** | Auto-detects React, Next.js, Vue, Angular, Svelte, or plain HTML |\n| **Severity Classification** | Categorizes each violation as Critical, Major, or Minor |\n| **Fix Code Generation** | Produces before\u002Fafter code diffs for every issue |\n| **Color Contrast Checker** | Validates foreground\u002Fbackground pairs against AA and AAA ratios |\n| **Accessible Alternatives** | Suggests replacement colors that meet contrast requirements |\n| **Compliance Reporting** | Generates stakeholder reports with pass\u002Ffail summaries |\n| **CI\u002FCD Integration** | GitHub Actions, GitLab CI, Azure DevOps pipeline configs |\n| **Keyboard Navigation Audit** | Detects missing focus management and tab order issues |\n| **ARIA Validation** | Checks for incorrect, redundant, or missing ARIA attributes |\n| **Live Region Detection** | Identifies dynamic content lacking `aria-live` announcements |\n| **Form Accessibility** | Validates label associations, error messaging, and input types |\n\n### WCAG 2.2 Coverage Matrix\n\n| Principle | Level A Criteria | Level AA Criteria |\n|-----------|-----------------|-------------------|\n| **Perceivable** | 1.1.1 Non-text Content, 1.2.1-1.2.3 Time-based Media, 1.3.1-1.3.3 Adaptable, 1.4.1-1.4.2 Distinguishable | 1.3.4-1.3.5 Adaptable, 1.4.3-1.4.5 Contrast & Images of Text, 1.4.10-1.4.13 Reflow & Content |\n| **Operable** | 2.1.1-2.1.2 Keyboard, 2.2.1-2.2.2 Timing, 2.3.1 Seizures, 2.4.1-2.4.4 Navigable, 2.5.1-2.5.4 Input | 2.4.5-2.4.7 Navigable, 2.4.11 Focus Appearance (NEW 2.2), 2.5.7 Dragging (NEW 2.2), 2.5.8 Target Size (NEW 2.2) |\n| **Understandable** | 3.1.1 Language, 3.2.1-3.2.2 Predictable, 3.3.1-3.3.2 Input Assistance | 3.1.2 Language of Parts, 3.2.3-3.2.4 Predictable, 3.3.3-3.3.4 Error Handling, 3.3.7 Redundant Entry (NEW 2.2), 3.3.8 Accessible Auth (NEW 2.2) |\n| **Robust** | 4.1.2 Name\u002FRole\u002FValue | 4.1.3 Status Messages |\n\n### Severity Definitions\n\n| Severity | Definition | Example | SLA |\n|----------|-----------|---------|-----|\n| **Critical** | Blocks access for entire user groups | Missing alt text on informational images, no keyboard access to primary navigation | Fix before release |\n| **Major** | Significant barrier that degrades experience | Insufficient color contrast on body text, missing form labels | Fix within current sprint |\n| **Minor** | Usability issue that causes friction | Redundant ARIA roles, suboptimal heading hierarchy | Fix within next 2 sprints |\n\n## Usage\n\n### Quick Start\n\nActivate the skill and run an audit on your project:\n\n```bash\n# Scan entire project\npython scripts\u002Fa11y_scanner.py \u002Fpath\u002Fto\u002Fproject\n\n# Scan with JSON output for tooling\npython scripts\u002Fa11y_scanner.py \u002Fpath\u002Fto\u002Fproject --json\n\n# Check color contrast for specific values\npython scripts\u002Fcontrast_checker.py --fg \"#777777\" --bg \"#ffffff\"\n\n# Check contrast across a CSS\u002FTailwind file\npython scripts\u002Fcontrast_checker.py --file \u002Fpath\u002Fto\u002Fstyles.css\n```\n\n### Slash Command\n\nUse the `\u002Fa11y-audit` slash command for an interactive audit session:\n\n```\n\u002Fa11y-audit                    # Audit current project\n\u002Fa11y-audit --scope src\u002F       # Audit specific directory\n\u002Fa11y-audit --fix              # Audit and auto-apply fixes\n\u002Fa11y-audit --report           # Generate stakeholder report\n\u002Fa11y-audit --ci               # Output CI-compatible results\n```\n\n### Three-Phase Workflow\n\n#### Phase 1: Scan\n\nThe scanner walks your source tree, detects the framework in use, and applies the appropriate rule set.\n\n```bash\npython scripts\u002Fa11y_scanner.py \u002Fpath\u002Fto\u002Fproject --format table\n```\n\n**Sample output:**\n\n```\nA11Y AUDIT REPORT - \u002Fpath\u002Fto\u002Fproject\nFramework Detected: React (Next.js)\nFiles Scanned: 127\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nCRITICAL (3 issues)\n  [1.1.1] src\u002Fcomponents\u002FHero.tsx:14\n    Missing alt text on \u003Cimg> element\n  [2.1.1] src\u002Fcomponents\u002FModal.tsx:8\n    Focus not trapped inside modal dialog\n  [1.4.3] src\u002Fstyles\u002Fglobals.css:42\n    Contrast ratio 2.8:1 on .subtitle (requires 4.5:1)\n\nMAJOR (7 issues)\n  [2.4.11] src\u002Fcomponents\u002FButton.tsx:22\n    Focus indicator not visible (2px outline required)\n  [1.3.1] src\u002Fcomponents\u002FForm.tsx:31\n    Input missing associated \u003Clabel>\n  ...\n\nMINOR (4 issues)\n  [1.3.1] src\u002Fcomponents\u002FNav.tsx:5\n    \u003Cnav> has redundant role=\"navigation\"\n  ...\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nSUMMARY: 14 violations (3 Critical, 7 Major, 4 Minor)\nWCAG 2.2 Level A:  8 issues\nWCAG 2.2 Level AA: 6 issues\n```\n\n#### Phase 2: Fix\n\nFor each violation, apply the framework-specific fix. The skill provides exact before\u002Fafter code for every issue type.\n\nSee the [Fix Patterns by Framework](#fix-patterns-by-framework) section below for the complete fix catalog.\n\n#### Phase 3: Verify\n\nRe-run the scanner to confirm all fixes are applied and no regressions were introduced:\n\n```bash\n# Re-scan after fixes\npython scripts\u002Fa11y_scanner.py \u002Fpath\u002Fto\u002Fproject --format table\n\n# Compare against baseline\npython scripts\u002Fa11y_scanner.py \u002Fpath\u002Fto\u002Fproject --baseline audit-baseline.json\n```\n\n**Verification output:**\n\n```\nVERIFICATION RESULTS\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nPrevious Scan:  14 violations (3 Critical, 7 Major, 4 Minor)\nCurrent Scan:    2 violations (0 Critical, 1 Major, 1 Minor)\nResolved:       12 violations\nNew Issues:      0 regressions\n\nSTATUS: IMPROVED (85.7% reduction)\n```\n\n## Examples\n\n### Example 1: React Component Audit\n\nGiven a React component with multiple accessibility issues:\n\n```tsx\n\u002F\u002F BEFORE: src\u002Fcomponents\u002FProductCard.tsx\nfunction ProductCard({ product }) {\n  return (\n    \u003Cdiv onClick={() => navigate(`\u002Fproduct\u002F${product.id}`)}>\n      \u003Cimg src={product.image} \u002F>\n      \u003Cdiv style={{ color: '#aaa', fontSize: '12px' }}>\n        {product.name}\n      \u003C\u002Fdiv>\n      \u003Cspan style={{ color: '#999' }}>${product.price}\u003C\u002Fspan>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n**Violations detected:**\n\n| # | WCAG | Severity | Issue |\n|---|------|----------|-------|\n| 1 | 1.1.1 | Critical | `\u003Cimg>` missing `alt` attribute |\n| 2 | 2.1.1 | Critical | `\u003Cdiv onClick>` not keyboard accessible |\n| 3 | 1.4.3 | Major | Color `#aaa` on white fails contrast (2.32:1, needs 4.5:1) |\n| 4 | 1.4.3 | Major | Color `#999` on white fails contrast (2.85:1, needs 4.5:1) |\n| 5 | 4.1.2 | Major | Interactive element missing role and accessible name |\n\n```tsx\n\u002F\u002F AFTER: src\u002Fcomponents\u002FProductCard.tsx\nfunction ProductCard({ product }) {\n  return (\n    \u003Ca\n      href={`\u002Fproduct\u002F${product.id}`}\n      className=\"product-card\"\n      aria-label={`View ${product.name} - $${product.price}`}\n    >\n      \u003Cimg src={product.image} alt={product.imageAlt || product.name} \u002F>\n      \u003Cdiv style={{ color: '#595959', fontSize: '12px' }}>\n        {product.name}\n      \u003C\u002Fdiv>\n      \u003Cspan style={{ color: '#767676' }}>${product.price}\u003C\u002Fspan>\n    \u003C\u002Fa>\n  );\n}\n```\n\n**What changed:**\n- `\u003Cdiv onClick>` replaced with `\u003Ca href>` for native keyboard and screen reader support\n- `alt` attribute added to `\u003Cimg>` with meaningful fallback\n- `aria-label` provides full context for assistive technology\n- Color `#aaa` replaced with `#595959` (7.01:1 contrast ratio -- passes AA and AAA)\n- Color `#999` replaced with `#767676` (4.54:1 contrast ratio -- passes AA)\n\n### Example 2: Vue SFC Form Audit\n\n```vue\n\u003C!-- BEFORE: src\u002Fcomponents\u002FLoginForm.vue -->\n\u003Ctemplate>\n  \u003Cform @submit=\"handleLogin\">\n    \u003Cinput type=\"text\" placeholder=\"Email\" v-model=\"email\" \u002F>\n    \u003Cinput type=\"password\" placeholder=\"Password\" v-model=\"password\" \u002F>\n    \u003Cdiv v-if=\"error\" style=\"color: red\">{{ error }}\u003C\u002Fdiv>\n    \u003Cdiv @click=\"handleLogin\">Sign In\u003C\u002Fdiv>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n```\n\n**Violations detected:**\n\n| # | WCAG | Severity | Issue |\n|---|------|----------|-------|\n| 1 | 1.3.1 | Critical | Inputs missing associated `\u003Clabel>` elements |\n| 2 | 3.3.2 | Major | Placeholder text used as only label (disappears on input) |\n| 3 | 2.1.1 | Critical | `\u003Cdiv @click>` not keyboard accessible |\n| 4 | 4.1.3 | Major | Error message not announced to screen readers |\n| 5 | 3.3.1 | Major | Error not programmatically associated with input |\n\n```vue\n\u003C!-- AFTER: src\u002Fcomponents\u002FLoginForm.vue -->\n\u003Ctemplate>\n  \u003Cform @submit.prevent=\"handleLogin\" aria-label=\"Sign in to your account\">\n    \u003Cdiv class=\"field\">\n      \u003Clabel for=\"login-email\">Email\u003C\u002Flabel>\n      \u003Cinput\n        id=\"login-email\"\n        type=\"email\"\n        v-model=\"email\"\n        autocomplete=\"email\"\n        required\n        :aria-describedby=\"emailError ? 'email-error' : undefined\"\n        :aria-invalid=\"!!emailError\"\n      \u002F>\n      \u003Cspan v-if=\"emailError\" id=\"email-error\" role=\"alert\">\n        {{ emailError }}\n      \u003C\u002Fspan>\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"field\">\n      \u003Clabel for=\"login-password\">Password\u003C\u002Flabel>\n      \u003Cinput\n        id=\"login-password\"\n        type=\"password\"\n        v-model=\"password\"\n        autocomplete=\"current-password\"\n        required\n        :aria-describedby=\"passwordError ? 'password-error' : undefined\"\n        :aria-invalid=\"!!passwordError\"\n      \u002F>\n      \u003Cspan v-if=\"passwordError\" id=\"password-error\" role=\"alert\">\n        {{ passwordError }}\n      \u003C\u002Fspan>\n    \u003C\u002Fdiv>\n    \u003Cdiv v-if=\"error\" role=\"alert\" aria-live=\"assertive\" class=\"form-error\">\n      {{ error }}\n    \u003C\u002Fdiv>\n    \u003Cbutton type=\"submit\">Sign In\u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n```\n\n### Example 3: Angular Template Audit\n\n```html\n\u003C!-- BEFORE: src\u002Fapp\u002Fdashboard\u002Fdashboard.component.html -->\n\u003Cdiv class=\"tabs\">\n  \u003Cdiv *ngFor=\"let tab of tabs\"\n       (click)=\"selectTab(tab)\"\n       [class.active]=\"tab.active\">\n    {{ tab.label }}\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\u003Cdiv class=\"tab-content\">\n  \u003Cdiv *ngIf=\"selectedTab\">{{ selectedTab.content }}\u003C\u002Fdiv>\n\u003C\u002Fdiv>\n```\n\n**Violations detected:**\n\n| # | WCAG | Severity | Issue |\n|---|------|----------|-------|\n| 1 | 4.1.2 | Critical | Tab widget missing ARIA roles (`tablist`, `tab`, `tabpanel`) |\n| 2 | 2.1.1 | Critical | Tabs not keyboard navigable (arrow keys, Home, End) |\n| 3 | 2.4.11 | Major | No visible focus indicator on active tab |\n\n```html\n\u003C!-- AFTER: src\u002Fapp\u002Fdashboard\u002Fdashboard.component.html -->\n\u003Cdiv class=\"tabs\" role=\"tablist\" aria-label=\"Dashboard sections\">\n  \u003Cbutton\n    *ngFor=\"let tab of tabs; let i = index\"\n    role=\"tab\"\n    [id]=\"'tab-' + tab.id\"\n    [attr.aria-selected]=\"tab.active\"\n    [attr.aria-controls]=\"'panel-' + tab.id\"\n    [attr.tabindex]=\"tab.active ? 0 : -1\"\n    (click)=\"selectTab(tab)\"\n    (keydown)=\"handleTabKeydown($event, i)\"\n    class=\"tab-button\"\n    [class.active]=\"tab.active\">\n    {{ tab.label }}\n  \u003C\u002Fbutton>\n\u003C\u002Fdiv>\n\u003Cdiv\n  *ngIf=\"selectedTab\"\n  role=\"tabpanel\"\n  [id]=\"'panel-' + selectedTab.id\"\n  [attr.aria-labelledby]=\"'tab-' + selectedTab.id\"\n  tabindex=\"0\"\n  class=\"tab-content\">\n  {{ selectedTab.content }}\n\u003C\u002Fdiv>\n```\n\n**Supporting TypeScript for keyboard navigation:**\n\n```typescript\n\u002F\u002F dashboard.component.ts\nhandleTabKeydown(event: KeyboardEvent, index: number): void {\n  const tabCount = this.tabs.length;\n  let newIndex = index;\n\n  switch (event.key) {\n    case 'ArrowRight':\n      newIndex = (index + 1) % tabCount;\n      break;\n    case 'ArrowLeft':\n      newIndex = (index - 1 + tabCount) % tabCount;\n      break;\n    case 'Home':\n      newIndex = 0;\n      break;\n    case 'End':\n      newIndex = tabCount - 1;\n      break;\n    default:\n      return;\n  }\n\n  event.preventDefault();\n  this.selectTab(this.tabs[newIndex]);\n  \u002F\u002F Move focus to the new tab button\n  const tabElement = document.getElementById(`tab-${this.tabs[newIndex].id}`);\n  tabElement?.focus();\n}\n```\n\n### Example 4: Next.js Page-Level Audit\n\n```tsx\n\u002F\u002F BEFORE: src\u002Fapp\u002Fpage.tsx\nexport default function Home() {\n  return (\n    \u003Cmain>\n      \u003Cdiv className=\"text-4xl font-bold\">Welcome to Acme\u003C\u002Fdiv>\n      \u003Cdiv className=\"mt-4\">\n        Build better products with our platform.\n      \u003C\u002Fdiv>\n      \u003Cdiv className=\"mt-8 bg-blue-600 text-white px-6 py-3 rounded cursor-pointer\"\n           onClick={() => router.push('\u002Fsignup')}>\n        Get Started\n      \u003C\u002Fdiv>\n    \u003C\u002Fmain>\n  );\n}\n```\n\n**Violations detected:**\n\n| # | WCAG | Severity | Issue |\n|---|------|----------|-------|\n| 1 | 1.3.1 | Major | Heading uses `\u003Cdiv>` instead of `\u003Ch1>` -- no semantic structure |\n| 2 | 2.4.2 | Major | Page missing `\u003Ctitle>` (Next.js metadata) |\n| 3 | 2.1.1 | Critical | CTA uses `\u003Cdiv onClick>` -- not keyboard accessible |\n| 4 | 3.1.1 | Minor | `\u003Chtml>` missing `lang` attribute (check `layout.tsx`) |\n\n```tsx\n\u002F\u002F AFTER: src\u002Fapp\u002Fpage.tsx\nimport type { Metadata } from 'next';\nimport Link from 'next\u002Flink';\n\nexport const metadata: Metadata = {\n  title: 'Acme - Build Better Products',\n  description: 'Build better products with the Acme platform.',\n};\n\nexport default function Home() {\n  return (\n    \u003Cmain>\n      \u003Ch1 className=\"text-4xl font-bold\">Welcome to Acme\u003C\u002Fh1>\n      \u003Cp className=\"mt-4\">\n        Build better products with our platform.\n      \u003C\u002Fp>\n      \u003CLink\n        href=\"\u002Fsignup\"\n        className=\"mt-8 inline-block bg-blue-600 text-white px-6 py-3 rounded\n                   hover:bg-blue-700 focus-visible:outline focus-visible:outline-2\n                   focus-visible:outline-offset-2 focus-visible:outline-blue-600\"\n      >\n        Get Started\n      \u003C\u002FLink>\n    \u003C\u002Fmain>\n  );\n}\n```\n\n```tsx\n\u002F\u002F Also fix: src\u002Fapp\u002Flayout.tsx\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    \u003Chtml lang=\"en\">\n      \u003Cbody>{children}\u003C\u002Fbody>\n    \u003C\u002Fhtml>\n  );\n}\n```\n\n### Example 5: Svelte Component Audit\n\n```svelte\n\u003C!-- BEFORE: src\u002Flib\u002Fcomponents\u002FAccordion.svelte -->\n\u003Cscript>\n  let openIndex = -1;\n  function toggle(i) { openIndex = openIndex === i ? -1 : i; }\n\u003C\u002Fscript>\n\n{#each items as item, i}\n  \u003Cdiv class=\"header\" on:click={() => toggle(i)}>\n    {item.title}\n  \u003C\u002Fdiv>\n  {#if openIndex === i}\n    \u003Cdiv class=\"content\">{item.body}\u003C\u002Fdiv>\n  {\u002Fif}\n{\u002Feach}\n```\n\n**Violations detected:**\n\n| # | WCAG | Severity | Issue |\n|---|------|----------|-------|\n| 1 | 4.1.2 | Critical | Accordion missing ARIA roles and properties |\n| 2 | 2.1.1 | Critical | Headers not keyboard accessible |\n| 3 | 2.5.8 | Minor | Click targets may be smaller than 24x24px (NEW in WCAG 2.2) |\n\n```svelte\n\u003C!-- AFTER: src\u002Flib\u002Fcomponents\u002FAccordion.svelte -->\n\u003Cscript>\n  export let items = [];\n  let openIndex = -1;\n\n  function toggle(i) {\n    openIndex = openIndex === i ? -1 : i;\n  }\n\n  function handleKeydown(event, i) {\n    if (event.key === 'Enter' || event.key === ' ') {\n      event.preventDefault();\n      toggle(i);\n    }\n  }\n\u003C\u002Fscript>\n\n\u003Cdiv class=\"accordion\">\n  {#each items as item, i}\n    \u003Ch3>\n      \u003Cbutton\n        class=\"accordion-header\"\n        aria-expanded={openIndex === i}\n        aria-controls=\"panel-{i}\"\n        id=\"header-{i}\"\n        on:click={() => toggle(i)}\n        on:keydown={(e) => handleKeydown(e, i)}\n      >\n        {item.title}\n        \u003Cspan class=\"icon\" aria-hidden=\"true\">\n          {openIndex === i ? '−' : '+'}\n        \u003C\u002Fspan>\n      \u003C\u002Fbutton>\n    \u003C\u002Fh3>\n    \u003Cdiv\n      id=\"panel-{i}\"\n      role=\"region\"\n      aria-labelledby=\"header-{i}\"\n      class=\"accordion-content\"\n      class:open={openIndex === i}\n      hidden={openIndex !== i}\n    >\n      {item.body}\n    \u003C\u002Fdiv>\n  {\u002Feach}\n\u003C\u002Fdiv>\n\n\u003Cstyle>\n  .accordion-header {\n    min-height: 44px; \u002F* WCAG 2.5.8 Target Size *\u002F\n    width: 100%;\n    padding: 12px 16px;\n    cursor: pointer;\n    text-align: left;\n  }\n  .accordion-header:focus-visible {\n    outline: 2px solid #005fcc;\n    outline-offset: 2px;\n  }\n\u003C\u002Fstyle>\n```\n\n## Fix Patterns by Framework\n\n### React \u002F Next.js Fix Patterns\n\n#### Missing Alt Text (1.1.1)\n\n```tsx\n\u002F\u002F BEFORE\n\u003Cimg src={hero} \u002F>\n\n\u002F\u002F AFTER - Informational image\n\u003Cimg src={hero} alt=\"Team collaborating around a whiteboard\" \u002F>\n\n\u002F\u002F AFTER - Decorative image\n\u003Cimg src={divider} alt=\"\" role=\"presentation\" \u002F>\n```\n\n#### Non-Interactive Element with Click Handler (2.1.1)\n\n```tsx\n\u002F\u002F BEFORE\n\u003Cdiv onClick={handleClick}>Click me\u003C\u002Fdiv>\n\n\u002F\u002F AFTER - If it navigates\n\u003CLink href=\"\u002Fdestination\">Click me\u003C\u002FLink>\n\n\u002F\u002F AFTER - If it performs an action\n\u003Cbutton type=\"button\" onClick={handleClick}>Click me\u003C\u002Fbutton>\n```\n\n#### Missing Focus Management in Modals (2.4.3)\n\n```tsx\n\u002F\u002F BEFORE\nfunction Modal({ isOpen, onClose, children }) {\n  if (!isOpen) return null;\n  return \u003Cdiv className=\"modal-overlay\">{children}\u003C\u002Fdiv>;\n}\n\n\u002F\u002F AFTER\nimport { useEffect, useRef } from 'react';\n\nfunction Modal({ isOpen, onClose, children, title }) {\n  const modalRef = useRef(null);\n  const previousFocus = useRef(null);\n\n  useEffect(() => {\n    if (isOpen) {\n      previousFocus.current = document.activeElement;\n      modalRef.current?.focus();\n    } else {\n      previousFocus.current?.focus();\n    }\n  }, [isOpen]);\n\n  useEffect(() => {\n    if (!isOpen) return;\n    const handleKeydown = (e) => {\n      if (e.key === 'Escape') onClose();\n      if (e.key === 'Tab') {\n        const focusable = modalRef.current?.querySelectorAll(\n          'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n        );\n        if (!focusable?.length) return;\n        const first = focusable[0];\n        const last = focusable[focusable.length - 1];\n        if (e.shiftKey && document.activeElement === first) {\n          e.preventDefault();\n          last.focus();\n        } else if (!e.shiftKey && document.activeElement === last) {\n          e.preventDefault();\n          first.focus();\n        }\n      }\n    };\n    document.addEventListener('keydown', handleKeydown);\n    return () => document.removeEventListener('keydown', handleKeydown);\n  }, [isOpen, onClose]);\n\n  if (!isOpen) return null;\n\n  return (\n    \u003Cdiv className=\"modal-overlay\" onClick={onClose} aria-hidden=\"true\">\n      \u003Cdiv\n        ref={modalRef}\n        role=\"dialog\"\n        aria-modal=\"true\"\n        aria-label={title}\n        tabIndex={-1}\n        onClick={(e) => e.stopPropagation()}\n      >\n        \u003Cbutton\n          onClick={onClose}\n          aria-label=\"Close dialog\"\n          className=\"modal-close\"\n        >\n          &times;\n        \u003C\u002Fbutton>\n        {children}\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n#### Focus Appearance (2.4.11 -- NEW in WCAG 2.2)\n\n```css\n\u002F* BEFORE *\u002F\nbutton:focus {\n  outline: none; \u002F* Removes default focus indicator *\u002F\n}\n\n\u002F* AFTER - Meets WCAG 2.2 Focus Appearance *\u002F\nbutton:focus-visible {\n  outline: 2px solid #005fcc;\n  outline-offset: 2px;\n}\n```\n\n```tsx\n\u002F\u002F Tailwind CSS pattern\n\u003Cbutton className=\"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600\">\n  Submit\n\u003C\u002Fbutton>\n```\n\n### Vue Fix Patterns\n\n#### Missing Form Labels (1.3.1)\n\n```vue\n\u003C!-- BEFORE -->\n\u003Cinput type=\"text\" v-model=\"name\" placeholder=\"Name\" \u002F>\n\n\u003C!-- AFTER -->\n\u003Clabel for=\"user-name\">Name\u003C\u002Flabel>\n\u003Cinput id=\"user-name\" type=\"text\" v-model=\"name\" autocomplete=\"name\" \u002F>\n```\n\n#### Dynamic Content Without Live Region (4.1.3)\n\n```vue\n\u003C!-- BEFORE -->\n\u003Cdiv v-if=\"status\">{{ statusMessage }}\u003C\u002Fdiv>\n\n\u003C!-- AFTER -->\n\u003Cdiv aria-live=\"polite\" aria-atomic=\"true\">\n  \u003Cp v-if=\"status\">{{ statusMessage }}\u003C\u002Fp>\n\u003C\u002Fdiv>\n```\n\n#### Vue Router Navigation Announcements (2.4.2)\n\n```typescript\n\u002F\u002F router\u002Findex.ts\nrouter.afterEach((to) => {\n  const title = to.meta.title || 'Page';\n  document.title = `${title} | My App`;\n\n  \u002F\u002F Announce route change to screen readers\n  const announcer = document.getElementById('route-announcer');\n  if (announcer) {\n    announcer.textContent = `Navigated to ${title}`;\n  }\n});\n```\n\n```vue\n\u003C!-- App.vue - Add announcer element -->\n\u003Cdiv\n  id=\"route-announcer\"\n  role=\"status\"\n  aria-live=\"assertive\"\n  aria-atomic=\"true\"\n  class=\"sr-only\"\n>\u003C\u002Fdiv>\n```\n\n### Angular Fix Patterns\n\n#### Missing ARIA on Custom Components (4.1.2)\n\n```typescript\n\u002F\u002F BEFORE\n@Component({\n  selector: 'app-dropdown',\n  template: `\n    \u003Cdiv (click)=\"toggle()\">{{ selected }}\u003C\u002Fdiv>\n    \u003Cdiv *ngIf=\"isOpen\">\n      \u003Cdiv *ngFor=\"let opt of options\" (click)=\"select(opt)\">{{ opt }}\u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  `\n})\n\n\u002F\u002F AFTER\n@Component({\n  selector: 'app-dropdown',\n  template: `\n    \u003Cbutton\n      role=\"combobox\"\n      [attr.aria-expanded]=\"isOpen\"\n      aria-haspopup=\"listbox\"\n      [attr.aria-label]=\"label\"\n      (click)=\"toggle()\"\n      (keydown)=\"handleKeydown($event)\"\n    >\n      {{ selected }}\n    \u003C\u002Fbutton>\n    \u003Cul *ngIf=\"isOpen\" role=\"listbox\" [attr.aria-label]=\"label + ' options'\">\n      \u003Cli\n        *ngFor=\"let opt of options; let i = index\"\n        role=\"option\"\n        [attr.aria-selected]=\"opt === selected\"\n        [attr.id]=\"'option-' + i\"\n        (click)=\"select(opt)\"\n        (keydown)=\"handleOptionKeydown($event, opt, i)\"\n        tabindex=\"-1\"\n      >\n        {{ opt }}\n      \u003C\u002Fli>\n    \u003C\u002Ful>\n  `\n})\n```\n\n#### Angular CDK A11y Module Integration\n\n```typescript\n\u002F\u002F Use Angular CDK for focus trap in dialogs\nimport { A11yModule } from '@angular\u002Fcdk\u002Fa11y';\n\n@Component({\n  template: `\n    \u003Cdiv cdkTrapFocus cdkTrapFocusAutoCapture>\n      \u003Ch2 id=\"dialog-title\">Edit Profile\u003C\u002Fh2>\n      \u003C!-- dialog content -->\n    \u003C\u002Fdiv>\n  `\n})\n```\n\n### Svelte Fix Patterns\n\n#### Accessible Announcements (4.1.3)\n\n```svelte\n\u003C!-- BEFORE -->\n{#if message}\n  \u003Cp class=\"toast\">{message}\u003C\u002Fp>\n{\u002Fif}\n\n\u003C!-- AFTER -->\n\u003Cdiv aria-live=\"polite\" class=\"sr-only\">\n  {#if message}\n    \u003Cp>{message}\u003C\u002Fp>\n  {\u002Fif}\n\u003C\u002Fdiv>\n\u003Cdiv class=\"toast\" aria-hidden=\"true\">\n  {#if message}\n    \u003Cp>{message}\u003C\u002Fp>\n  {\u002Fif}\n\u003C\u002Fdiv>\n```\n\n#### SvelteKit Page Titles (2.4.2)\n\n```svelte\n\u003C!-- +page.svelte -->\n\u003Csvelte:head>\n  \u003Ctitle>Dashboard | My App\u003C\u002Ftitle>\n\u003C\u002Fsvelte:head>\n```\n\n### Plain HTML Fix Patterns\n\n#### Skip Navigation Link (2.4.1)\n\n```html\n\u003C!-- BEFORE -->\n\u003Cbody>\n  \u003Cnav>\u003C!-- long navigation -->\u003C\u002Fnav>\n  \u003Cmain>\u003C!-- content -->\u003C\u002Fmain>\n\u003C\u002Fbody>\n\n\u003C!-- AFTER -->\n\u003Cbody>\n  \u003Ca href=\"#main-content\" class=\"skip-link\">Skip to main content\u003C\u002Fa>\n  \u003Cnav aria-label=\"Main navigation\">\u003C!-- long navigation -->\u003C\u002Fnav>\n  \u003Cmain id=\"main-content\" tabindex=\"-1\">\u003C!-- content -->\u003C\u002Fmain>\n\u003C\u002Fbody>\n```\n\n```css\n.skip-link {\n  position: absolute;\n  top: -40px;\n  left: 0;\n  padding: 8px 16px;\n  background: #005fcc;\n  color: #fff;\n  z-index: 1000;\n  transition: top 0.2s;\n}\n.skip-link:focus {\n  top: 0;\n}\n```\n\n#### Accessible Data Table (1.3.1)\n\n```html\n\u003C!-- BEFORE -->\n\u003Ctable>\n  \u003Ctr>\u003Ctd>Name\u003C\u002Ftd>\u003Ctd>Email\u003C\u002Ftd>\u003Ctd>Role\u003C\u002Ftd>\u003C\u002Ftr>\n  \u003Ctr>\u003Ctd>Alice\u003C\u002Ftd>\u003Ctd>alice@co.com\u003C\u002Ftd>\u003Ctd>Admin\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftable>\n\n\u003C!-- AFTER -->\n\u003Ctable aria-label=\"Team members\">\n  \u003Ccaption class=\"sr-only\">List of team members and their roles\u003C\u002Fcaption>\n  \u003Cthead>\n    \u003Ctr>\n      \u003Cth scope=\"col\">Name\u003C\u002Fth>\n      \u003Cth scope=\"col\">Email\u003C\u002Fth>\n      \u003Cth scope=\"col\">Role\u003C\u002Fth>\n    \u003C\u002Ftr>\n  \u003C\u002Fthead>\n  \u003Ctbody>\n    \u003Ctr>\n      \u003Cth scope=\"row\">Alice\u003C\u002Fth>\n      \u003Ctd>alice@co.com\u003C\u002Ftd>\n      \u003Ctd>Admin\u003C\u002Ftd>\n    \u003C\u002Ftr>\n  \u003C\u002Ftbody>\n\u003C\u002Ftable>\n```\n\n## Color Contrast Checker\n\nThe `contrast_checker.py` script validates color pairs against WCAG 2.2 contrast requirements.\n\n### Usage\n\n```bash\n# Check a single color pair\npython scripts\u002Fcontrast_checker.py --fg \"#777777\" --bg \"#ffffff\"\n\n# Output:\n# Foreground: #777777 | Background: #ffffff\n# Contrast Ratio: 4.48:1\n# AA Normal Text (4.5:1): FAIL\n# AA Large Text (3.0:1):  PASS\n# AAA Normal Text (7.0:1): FAIL\n# Suggested alternative: #767676 (4.54:1 - passes AA)\n\n# Scan a CSS file for all color pairs\npython scripts\u002Fcontrast_checker.py --file src\u002Fstyles\u002Fglobals.css\n\n# Scan Tailwind classes in components\npython scripts\u002Fcontrast_checker.py --tailwind src\u002Fcomponents\u002F\n```\n\n### Common Contrast Fixes\n\n| Original Color | Contrast on White | Fix | New Contrast |\n|----------------|------------------|-----|--------------|\n| `#aaaaaa` | 2.32:1 | `#767676` | 4.54:1 (AA) |\n| `#999999` | 2.85:1 | `#767676` | 4.54:1 (AA) |\n| `#888888` | 3.54:1 | `#767676` | 4.54:1 (AA) |\n| `#777777` | 4.48:1 | `#757575` | 4.60:1 (AA) |\n| `#66bb6a` | 3.06:1 | `#2e7d32` | 5.87:1 (AA) |\n| `#42a5f5` | 2.81:1 | `#1565c0` | 6.08:1 (AA) |\n| `#ef5350` | 3.13:1 | `#c62828` | 5.57:1 (AA) |\n\n### Tailwind CSS Accessible Palette Mapping\n\n| Inaccessible Class | Contrast on White | Accessible Alternative | Contrast |\n|---------------------|------------------|----------------------|----------|\n| `text-gray-400` | 2.68:1 | `text-gray-600` | 5.74:1 |\n| `text-blue-400` | 2.81:1 | `text-blue-700` | 5.96:1 |\n| `text-green-400` | 2.12:1 | `text-green-700` | 5.18:1 |\n| `text-red-400` | 3.04:1 | `text-red-700` | 6.05:1 |\n| `text-yellow-500` | 1.47:1 | `text-yellow-800` | 7.34:1 |\n\n## CI\u002FCD Integration\n\n### GitHub Actions\n\n```yaml\n# .github\u002Fworkflows\u002Fa11y-audit.yml\nname: Accessibility Audit\n\non:\n  pull_request:\n    paths:\n      - 'src\u002F**\u002F*.tsx'\n      - 'src\u002F**\u002F*.vue'\n      - 'src\u002F**\u002F*.html'\n      - 'src\u002F**\u002F*.svelte'\n\njobs:\n  a11y-audit:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - name: Set up Python\n        uses: actions\u002Fsetup-python@v5\n        with:\n          python-version: '3.11'\n\n      - name: Run A11y Scanner\n        run: |\n          python scripts\u002Fa11y_scanner.py .\u002Fsrc --json > a11y-results.json\n\n      - name: Check for Critical Issues\n        run: |\n          python -c \"\n          import json, sys\n          with open('a11y-results.json') as f:\n              data = json.load(f)\n          critical = [v for v in data.get('violations', []) if v['severity'] == 'critical']\n          if critical:\n              print(f'FAILED: {len(critical)} critical a11y violations found')\n              for v in critical:\n                  print(f\\\"  [{v['wcag']}] {v['file']}:{v['line']} - {v['message']}\\\")\n              sys.exit(1)\n          print('PASSED: No critical a11y violations')\n          \"\n\n      - name: Upload Audit Report\n        if: always()\n        uses: actions\u002Fupload-artifact@v4\n        with:\n          name: a11y-audit-report\n          path: a11y-results.json\n\n      - name: Comment on PR\n        if: failure()\n        uses: marocchino\u002Fsticky-pull-request-comment@v2\n        with:\n          header: a11y-audit\n          message: |\n            ## Accessibility Audit Failed\n            Critical WCAG 2.2 violations were found. See the uploaded artifact for details.\n            Run `python scripts\u002Fa11y_scanner.py .\u002Fsrc` locally to view and fix issues.\n```\n\n### GitLab CI\n\n```yaml\n# .gitlab-ci.yml\na11y-audit:\n  stage: test\n  image: python:3.11-slim\n  script:\n    - python scripts\u002Fa11y_scanner.py .\u002Fsrc --json > a11y-results.json\n    - python -c \"\n      import json, sys;\n      data = json.load(open('a11y-results.json'));\n      critical = [v for v in data.get('violations', []) if v['severity'] == 'critical'];\n      sys.exit(1) if critical else print('A11y audit passed')\n      \"\n  artifacts:\n    paths:\n      - a11y-results.json\n    when: always\n  rules:\n    - changes:\n        - \"src\u002F**\u002F*.{tsx,vue,html,svelte}\"\n```\n\n### Azure DevOps\n\n```yaml\n# azure-pipelines.yml\n- task: PythonScript@0\n  displayName: 'Run A11y Audit'\n  inputs:\n    scriptSource: 'filePath'\n    scriptPath: 'scripts\u002Fa11y_scanner.py'\n    arguments: '.\u002Fsrc --json --output $(Build.ArtifactStagingDirectory)\u002Fa11y-results.json'\n\n- task: PublishBuildArtifacts@1\n  condition: always()\n  inputs:\n    PathtoPublish: '$(Build.ArtifactStagingDirectory)\u002Fa11y-results.json'\n    ArtifactName: 'a11y-audit-report'\n```\n\n### Pre-Commit Hook\n\n```bash\n#!\u002Fbin\u002Fbash\n# .git\u002Fhooks\u002Fpre-commit\n\n# Run a11y scan on staged files only\nSTAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\\.(tsx|vue|html|svelte|jsx)$')\n\nif [ -n \"$STAGED_FILES\" ]; then\n  echo \"Running accessibility audit on staged files...\"\n  for file in $STAGED_FILES; do\n    python scripts\u002Fa11y_scanner.py \"$file\" --severity critical --quiet\n    if [ $? -ne 0 ]; then\n      echo \"A11y audit FAILED for $file. Fix critical issues before committing.\"\n      exit 1\n    fi\n  done\n  echo \"A11y audit passed.\"\nfi\n```\n\n## Common Pitfalls\n\n| Pitfall | Why It Happens | Correct Approach |\n|---------|---------------|------------------|\n| Using `role=\"button\"` on a `\u003Cdiv>` | Developers think ARIA makes any element interactive | Use a native `\u003Cbutton>` element instead -- it includes keyboard handling, focus, and click events for free |\n| Setting `tabindex=\"0\"` on everything | Attempting to make elements focusable | Only interactive elements need focus. Use native elements (`\u003Ca>`, `\u003Cbutton>`, `\u003Cinput>`) which are focusable by default |\n| Using `aria-label` on non-interactive elements | Trying to add descriptions to `\u003Cdiv>` or `\u003Cspan>` | Screen readers may ignore `aria-label` on generic elements. Use `aria-labelledby` pointing to visible text, or restructure with headings |\n| Hiding content with `display: none` for screen readers | Wanting visual hiding | `display: none` hides from ALL users, including screen readers. Use `.sr-only` class for screen-reader-only content |\n| Using color alone to convey meaning | Red\u002Fgreen for status, error states | Add icons, text labels, or patterns alongside color. WCAG 1.4.1 requires non-color indicators |\n| Placeholder text as the only label | Saves visual space | Placeholder disappears on input, fails 1.3.1 and 3.3.2. Always provide a visible `\u003Clabel>` |\n| Auto-playing video or audio | Engagement metrics | Violates 1.4.2. Never autoplay media with sound. Provide pause\u002Fstop controls |\n| `outline: none` without replacement | Design preference | Violates 2.4.7 and 2.4.11. Always provide a visible focus indicator, use `focus-visible` to limit to keyboard users |\n| Empty `alt=\"\"` on informational images | Misunderstanding empty alt | Empty alt marks images as decorative. Informational images need descriptive alt text |\n| Missing heading hierarchy (h1 -> h3, skipping h2) | Visual styling drives heading choice | Heading levels must be sequential. Use CSS for styling, HTML for structure |\n| `onClick` without `onKeyDown` on custom elements | Mouse-first development | Custom interactive elements need keyboard support. Prefer native elements or add `onKeyDown` with Enter\u002FSpace handling |\n| Inaccessible custom `\u003Cselect>` replacements | Design requirements override native controls | Custom dropdowns need full ARIA: `combobox`, `listbox`, `option` roles, plus keyboard navigation (arrows, type-ahead, Escape) |\n| Ignoring `prefers-reduced-motion` | Animations assumed safe for all | Wrap animations in `@media (prefers-reduced-motion: no-preference)` or provide reduced alternatives |\n| CAPTCHAs without alternatives | Bot prevention | Violates 3.3.8 (WCAG 2.2). Provide alternative verification methods (email, SMS) alongside visual CAPTCHAs |\n\n## Screen Reader Utility Class\n\nEvery project should include this utility class for visually hiding content while keeping it accessible to screen readers:\n\n```css\n\u002F* Visually hidden but accessible to screen readers *\u002F\n.sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip: rect(0, 0, 0, 0);\n  white-space: nowrap;\n  border-width: 0;\n}\n\n\u002F* Allow the element to be focusable when navigated to via keyboard *\u002F\n.sr-only-focusable:focus,\n.sr-only-focusable:active {\n  position: static;\n  width: auto;\n  height: auto;\n  padding: inherit;\n  margin: inherit;\n  overflow: visible;\n  clip: auto;\n  white-space: inherit;\n}\n```\n\nTailwind CSS includes this as `sr-only` by default. For other frameworks:\n- **Angular**: Add to `styles.scss`\n- **Vue**: Add to `assets\u002Fglobal.css`\n- **Svelte**: Add to `app.css`\n\n## Audit Report Template\n\nThe scanner generates a stakeholder-ready report when run with the `--report` flag:\n\n```bash\npython scripts\u002Fa11y_scanner.py \u002Fpath\u002Fto\u002Fproject --report --output audit-report.md\n```\n\n**Generated report structure:**\n\n```markdown\n# Accessibility Audit Report\n**Project:** Acme Dashboard\n**Date:** 2026-03-18\n**Standard:** WCAG 2.2 Level AA\n**Tool:** a11y-audit v2.1.2\n\n## Executive Summary\n- Files Scanned: 127\n- Total Violations: 14\n- Critical: 3 | Major: 7 | Minor: 4\n- Estimated Remediation: 8-12 hours\n- Compliance Score: 72% (Target: 100%)\n\n## Violations by Category\n| Category | Count | Severity Breakdown |\n|----------|-------|--------------------|\n| Missing Alt Text | 3 | 2 Critical, 1 Minor |\n| Keyboard Access | 4 | 2 Critical, 2 Major |\n| Color Contrast | 3 | 3 Major |\n| Form Labels | 2 | 2 Major |\n| ARIA Usage | 2 | 2 Minor |\n\n## Detailed Findings\n[Per-violation details with file, line, WCAG criterion, and fix]\n\n## Remediation Priority\n1. Fix all Critical issues (blocks release)\n2. Fix Major issues in current sprint\n3. Schedule Minor issues for next sprint\n\n## Recommendations\n- Add a11y linting to CI pipeline (eslint-plugin-jsx-a11y)\n- Include keyboard testing in QA checklist\n- Schedule quarterly manual audit with assistive technology\n```\n\n## Tools Reference\n\n### a11y_scanner.py\n\nScans source files for WCAG 2.2 violations.\n\n```\nUsage: python scripts\u002Fa11y_scanner.py \u003Cpath> [options]\n\nArguments:\n  path                    File or directory to scan\n\nOptions:\n  --json                  Output results as JSON\n  --format {table,csv}    Output format (default: table)\n  --severity {critical,major,minor}\n                          Filter by minimum severity\n  --framework {react,vue,angular,svelte,html,auto}\n                          Force framework (default: auto-detect)\n  --baseline FILE         Compare against previous scan results\n  --report                Generate stakeholder report\n  --output FILE           Write results to file\n  --quiet                 Suppress output, exit code only\n  --ci                    CI mode: non-zero exit on critical issues\n```\n\n### contrast_checker.py\n\nValidates color contrast ratios against WCAG 2.2 requirements.\n\n```\nUsage: python scripts\u002Fcontrast_checker.py [options]\n\nOptions:\n  --fg COLOR              Foreground color (hex)\n  --bg COLOR              Background color (hex)\n  --file FILE             Scan CSS file for color pairs\n  --tailwind DIR          Scan directory for Tailwind color classes\n  --json                  Output results as JSON\n  --suggest               Suggest accessible alternatives for failures\n  --level {aa,aaa}        Target conformance level (default: aa)\n```\n\n## Testing Checklist\n\nUse this checklist after applying fixes to verify accessibility manually:\n\n### Keyboard Navigation\n- [ ] All interactive elements reachable via Tab key\n- [ ] Tab order follows visual\u002Flogical reading order\n- [ ] Focus indicator visible on every focusable element (2px+ outline)\n- [ ] Modals trap focus and return focus on close\n- [ ] Escape key closes modals, dropdowns, and popups\n- [ ] Arrow keys navigate within composite widgets (tabs, menus, listboxes)\n- [ ] No keyboard traps (user can always Tab away)\n\n### Screen Reader\n- [ ] All images have appropriate alt text (or `alt=\"\"` for decorative)\n- [ ] Headings create logical document outline (h1 -> h2 -> h3)\n- [ ] Form inputs have associated labels\n- [ ] Error messages announced via `aria-live` or `role=\"alert\"`\n- [ ] Page title updates on navigation (SPA)\n- [ ] Dynamic content changes announced appropriately\n\n### Visual\n- [ ] Text contrast meets 4.5:1 for normal text, 3:1 for large text\n- [ ] UI component contrast meets 3:1 against background\n- [ ] Content reflows without horizontal scrolling at 320px width\n- [ ] Text resizable to 200% without loss of content\n- [ ] No information conveyed by color alone\n- [ ] Focus indicators meet 2.4.11 Focus Appearance criteria\n\n### Motion and Media\n- [ ] Animations respect `prefers-reduced-motion`\n- [ ] No auto-playing media with audio\n- [ ] No content flashing more than 3 times per second\n- [ ] Video has captions; audio has transcripts\n\n### Forms\n- [ ] All inputs have visible labels\n- [ ] Required fields indicated (not by color alone)\n- [ ] Error messages specific and associated with input via `aria-describedby`\n- [ ] Autocomplete attributes present on common fields (name, email, etc.)\n- [ ] No CAPTCHA without alternative method (WCAG 2.2 3.3.8)\n\n## WCAG 2.2 New Success Criteria Reference\n\nThese criteria were added in WCAG 2.2 and are commonly missed:\n\n### 2.4.11 Focus Appearance (Level AA)\n\nThe focus indicator must have a minimum area of a 2px perimeter around the component and a contrast ratio of at least 3:1 against adjacent colors.\n\n**Pattern:**\n```css\n:focus-visible {\n  outline: 2px solid #005fcc;\n  outline-offset: 2px;\n}\n```\n\n### 2.5.7 Dragging Movements (Level AA)\n\nAny functionality that uses dragging must have a single-pointer alternative (click, tap).\n\n**Pattern:**\n```tsx\n\u002F\u002F Sortable list: support both drag and button-based reorder\n\u003Cli draggable onDragStart={handleDrag}>\n  {item.name}\n  \u003Cbutton onClick={() => moveUp(index)} aria-label={`Move ${item.name} up`}>\n    Move Up\n  \u003C\u002Fbutton>\n  \u003Cbutton onClick={() => moveDown(index)} aria-label={`Move ${item.name} down`}>\n    Move Down\n  \u003C\u002Fbutton>\n\u003C\u002Fli>\n```\n\n### 2.5.8 Target Size (Level AA)\n\nInteractive targets must be at least 24x24 CSS pixels, with exceptions for inline text links and elements where the spacing provides equivalent clearance.\n\n**Pattern:**\n```css\nbutton, a, input, select, textarea {\n  min-height: 24px;\n  min-width: 24px;\n}\n\n\u002F* Recommended: 44x44px for touch targets *\u002F\n@media (pointer: coarse) {\n  button, a, input[type=\"checkbox\"], input[type=\"radio\"] {\n    min-height: 44px;\n    min-width: 44px;\n  }\n}\n```\n\n### 3.3.7 Redundant Entry (Level A)\n\nInformation previously entered by the user must be auto-populated or available for selection when needed again in the same process.\n\n**Pattern:**\n```tsx\n\u002F\u002F Multi-step form: persist data across steps\nconst [formData, setFormData] = useState({});\n\n\u002F\u002F Step 2 pre-fills shipping address from billing\n\u003Cinput\n  defaultValue={formData.billingAddress || ''}\n  autoComplete=\"shipping street-address\"\n\u002F>\n```\n\n### 3.3.8 Accessible Authentication (Level AA)\n\nAuthentication must not require cognitive function tests (e.g., remembering a password, solving a puzzle) unless an alternative is provided.\n\n**Pattern:**\n- Support password managers (`autocomplete=\"current-password\"`)\n- Offer passkey \u002F biometric authentication\n- Allow copy-paste in password fields (never block paste)\n- Provide email\u002FSMS OTP as alternative to CAPTCHA\n\n## Related Skills\n\n| Skill | Relationship | Path |\n|-------|-------------|------|\n| **senior-frontend** | Frontend patterns used in a11y fixes (React, Next.js, Tailwind) | `engineering-team\u002Fsenior-frontend\u002F` |\n| **code-reviewer** | Include a11y checks in code review workflows | `engineering-team\u002Fcode-reviewer\u002F` |\n| **senior-qa** | Integration of a11y testing into QA processes | `engineering-team\u002Fsenior-qa\u002F` |\n| **playwright-pro** | Automated browser testing with accessibility assertions | `engineering-team\u002Fplaywright-pro\u002F` |\n| **senior-secops** | Accessibility as part of compliance and security posture | `engineering-team\u002Fsenior-secops\u002F` |\n| **epic-design** | WCAG 2.1 AA compliant animations and scroll storytelling | `engineering-team\u002Fepic-design\u002F` |\n| **tdd-guide** | Test-driven development patterns for a11y test cases | `engineering-team\u002Ftdd-guide\u002F` |\n| **incident-commander** | Respond to a11y compliance incidents and legal risk | `engineering-team\u002Fincident-commander\u002F` |\n\n## Resources\n\n- [WCAG 2.2 Specification](https:\u002F\u002Fwww.w3.org\u002FTR\u002FWCAG22\u002F)\n- [WAI-ARIA Authoring Practices 1.2](https:\u002F\u002Fwww.w3.org\u002FWAI\u002FARIA\u002Fapg\u002F)\n- [Deque axe-core Rules](https:\u002F\u002Fgithub.com\u002Fdequelabs\u002Faxe-core\u002Fblob\u002Fdevelop\u002Fdoc\u002Frule-descriptions.md)\n- [eslint-plugin-jsx-a11y](https:\u002F\u002Fgithub.com\u002Fjsx-eslint\u002Feslint-plugin-jsx-a11y)\n- [vue-a11y](https:\u002F\u002Fvue-a11y.com\u002F)\n- [@angular-eslint\u002Ftemplate-accessibility](https:\u002F\u002Fgithub.com\u002Fangular-eslint\u002Fangular-eslint)\n\n---\n\n**License:** MIT\n**Author:** Alireza Rezvani\n**Version:** 2.1.2\n\n","# Accessibility Audit\n\n---\n\n**Name**: a11y-audit\n**Tier**: STANDARD\n**Category**: Engineering - Frontend Quality\n**Dependencies**: Python 3.8+ (Standard Library Only)\n**Author**: Alireza Rezvani\n**Version**: 2.1.2\n**Last Updated**: 2026-03-18\n**License**: MIT\n\n---\n\n## Name\n\na11y-audit -- WCAG 2.2 Accessibility Audit and Remediation Skill\n\n## Description\n\nThe a11y-audit skill provides a complete accessibility audit pipeline for modern web applications. It implements a three-phase workflow -- Scan, Fix, Verify -- that identifies WCAG 2.2 Level A and AA violations, generates exact fix code per framework, and produces stakeholder-ready compliance reports.\n\nThis skill goes beyond detection. For every violation it finds, it provides the precise before\u002Fafter code fix tailored to your framework (React, Next.js, Vue, Angular, Svelte, or plain HTML). It understands that a missing `alt` attribute on an `\u003Cimg>` in React JSX requires a different fix pattern than the same issue in a Vue SFC or an Angular template.\n\n**What this skill does:**\n\n1. **Scans** your codebase for every WCAG 2.2 Level A and AA violation, categorized by severity (Critical, Major, Minor)\n2. **Fixes** each violation with framework-specific before\u002Fafter code patterns\n3. **Verifies** that fixes resolve the original violations and introduces no regressions\n4. **Reports** findings in a structured format suitable for developers, PMs, and compliance stakeholders\n5. **Integrates** into CI\u002FCD pipelines to prevent accessibility regressions\n\n**Key differentiators:**\n\n- Framework-aware fix patterns (not generic HTML advice)\n- Color contrast analysis with accessible alternative suggestions\n- WCAG 2.2 coverage including the newest success criteria (Focus Appearance, Dragging Movements, Target Size)\n- CI\u002FCD pipeline integration with GitHub Actions, GitLab CI, and Azure DevOps\n- Slash command support via `\u002Fa11y-audit`\n\n## Features\n\n### Core Capabilities\n\n| Feature | Description |\n|---------|-------------|\n| **Full WCAG 2.2 Scan** | Checks all Level A and AA success criteria across your codebase |\n| **Framework Detection** | Auto-detects React, Next.js, Vue, Angular, Svelte, or plain HTML |\n| **Severity Classification** | Categorizes each violation as Critical, Major, or Minor |\n| **Fix Code Generation** | Produces before\u002Fafter code diffs for every issue |\n| **Color Contrast Checker** | Validates foreground\u002Fbackground pairs against AA and AAA ratios |\n| **Accessible Alternatives** | Suggests replacement colors that meet contrast requirements |\n| **Compliance Reporting** | Generates stakeholder reports with pass\u002Ffail summaries |\n| **CI\u002FCD Integration** | GitHub Actions, GitLab CI, Azure DevOps pipeline configs |\n| **Keyboard Navigation Audit** | Detects missing focus management and tab order issues |\n| **ARIA Validation** | Checks for incorrect, redundant, or missing ARIA attributes |\n| **Live Region Detection** | Identifies dynamic content lacking `aria-live` announcements |\n| **Form Accessibility** | Validates label associations, error messaging, and input types |\n\n### WCAG 2.2 Coverage Matrix\n\n| Principle | Level A Criteria | Level AA Criteria |\n|-----------|-----------------|-------------------|\n| **Perceivable** | 1.1.1 Non-text Content, 1.2.1-1.2.3 Time-based Media, 1.3.1-1.3.3 Adaptable, 1.4.1-1.4.2 Distinguishable | 1.3.4-1.3.5 Adaptable, 1.4.3-1.4.5 Contrast & Images of Text, 1.4.10-1.4.13 Reflow & Content |\n| **Operable** | 2.1.1-2.1.2 Keyboard, 2.2.1-2.2.2 Timing, 2.3.1 Seizures, 2.4.1-2.4.4 Navigable, 2.5.1-2.5.4 Input | 2.4.5-2.4.7 Navigable, 2.4.11 Focus Appearance (NEW 2.2), 2.5.7 Dragging (NEW 2.2), 2.5.8 Target Size (NEW 2.2) |\n| **Understandable** | 3.1.1 Language, 3.2.1-3.2.2 Predictable, 3.3.1-3.3.2 Input Assistance | 3.1.2 Language of Parts, 3.2.3-3.2.4 Predictable, 3.3.3-3.3.4 Error Handling, 3.3.7 Redundant Entry (NEW 2.2), 3.3.8 Accessible Auth (NEW 2.2) |\n| **Robust** | 4.1.2 Name\u002FRole\u002FValue | 4.1.3 Status Messages |\n\n### Severity Definitions\n\n| Severity | Definition | Example | SLA |\n|----------|-----------|---------|-----|\n| **Critical** | Blocks access for entire user groups | Missing alt text on informational images, no keyboard access to primary navigation | Fix before release |\n| **Major** | Significant barrier that degrades experience | Insufficient color contrast on body text, missing form labels | Fix within current sprint |\n| **Minor** | Usability issue that causes friction | Redundant ARIA roles, suboptimal heading hierarchy | Fix within next 2 sprints |\n\n## Usage\n\n### Quick Start\n\nActivate the skill and run an audit on your project:\n\n```bash\n# Scan entire project\npython scripts\u002Fa11y_scanner.py \u002Fpath\u002Fto\u002Fproject\n\n# Scan with JSON output for tooling\npython scripts\u002Fa11y_scanner.py \u002Fpath\u002Fto\u002Fproject --json\n\n# Check color contrast for specific values\npython scripts\u002Fcontrast_checker.py --fg \"#777777\" --bg \"#ffffff\"\n\n# Check contrast across a CSS\u002FTailwind file\npython scripts\u002Fcontrast_checker.py --file \u002Fpath\u002Fto\u002Fstyles.css\n```\n\n### Slash Command\n\nUse the `\u002Fa11y-audit` slash command for an interactive audit session:\n\n```\n\u002Fa11y-audit                    # Audit current project\n\u002Fa11y-audit --scope src\u002F       # Audit specific directory\n\u002Fa11y-audit --fix              # Audit and auto-apply fixes\n\u002Fa11y-audit --report           # Generate stakeholder report\n\u002Fa11y-audit --ci               # Output CI-compatible results\n```\n\n### Three-Phase Workflow\n\n#### Phase 1: Scan\n\nThe scanner walks your source tree, detects the framework in use, and applies the appropriate rule set.\n\n```bash\npython scripts\u002Fa11y_scanner.py \u002Fpath\u002Fto\u002Fproject --format table\n```\n\n**Sample output:**\n\n```\nA11Y AUDIT REPORT - \u002Fpath\u002Fto\u002Fproject\nFramework Detected: React (Next.js)\nFiles Scanned: 127\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nCRITICAL (3 issues)\n  [1.1.1] src\u002Fcomponents\u002FHero.tsx:14\n    Missing alt text on \u003Cimg> element\n  [2.1.1] src\u002Fcomponents\u002FModal.tsx:8\n    Focus not trapped inside modal dialog\n  [1.4.3] src\u002Fstyles\u002Fglobals.css:42\n    Contrast ratio 2.8:1 on .subtitle (requires 4.5:1)\n\nMAJOR (7 issues)\n  [2.4.11] src\u002Fcomponents\u002FButton.tsx:22\n    Focus indicator not visible (2px outline required)\n  [1.3.1] src\u002Fcomponents\u002FForm.tsx:31\n    Input missing associated \u003Clabel>\n  ...\n\nMINOR (4 issues)\n  [1.3.1] src\u002Fcomponents\u002FNav.tsx:5\n    \u003Cnav> has redundant role=\"navigation\"\n  ...\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nSUMMARY: 14 violations (3 Critical, 7 Major, 4 Minor)\nWCAG 2.2 Level A:  8 issues\nWCAG 2.2 Level AA: 6 issues\n```\n\n#### Phase 2: Fix\n\nFor each violation, apply the framework-specific fix. The skill provides exact before\u002Fafter code for every issue type.\n\nSee the [Fix Patterns by Framework](#fix-patterns-by-framework) section below for the complete fix catalog.\n\n#### Phase 3: Verify\n\nRe-run the scanner to confirm all fixes are applied and no regressions were introduced:\n\n```bash\n# Re-scan after fixes\npython scripts\u002Fa11y_scanner.py \u002Fpath\u002Fto\u002Fproject --format table\n\n# Compare against baseline\npython scripts\u002Fa11y_scanner.py \u002Fpath\u002Fto\u002Fproject --baseline audit-baseline.json\n```\n\n**Verification output:**\n\n```\nVERIFICATION RESULTS\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nPrevious Scan:  14 violations (3 Critical, 7 Major, 4 Minor)\nCurrent Scan:    2 violations (0 Critical, 1 Major, 1 Minor)\nResolved:       12 violations\nNew Issues:      0 regressions\n\nSTATUS: IMPROVED (85.7% reduction)\n```\n\n## Examples\n\n### Example 1: React Component Audit\n\nGiven a React component with multiple accessibility issues:\n\n```tsx\n\u002F\u002F BEFORE: src\u002Fcomponents\u002FProductCard.tsx\nfunction ProductCard({ product }) {\n  return (\n    \u003Cdiv onClick={() => navigate(`\u002Fproduct\u002F${product.id}`)}>\n      \u003Cimg src={product.image} \u002F>\n      \u003Cdiv style={{ color: '#aaa', fontSize: '12px' }}>\n        {product.name}\n      \u003C\u002Fdiv>\n      \u003Cspan style={{ color: '#999' }}>${product.price}\u003C\u002Fspan>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n**Violations detected:**\n\n| # | WCAG | Severity | Issue |\n|---|------|----------|-------|\n| 1 | 1.1.1 | Critical | `\u003Cimg>` missing `alt` attribute |\n| 2 | 2.1.1 | Critical | `\u003Cdiv onClick>` not keyboard accessible |\n| 3 | 1.4.3 | Major | Color `#aaa` on white fails contrast (2.32:1, needs 4.5:1) |\n| 4 | 1.4.3 | Major | Color `#999` on white fails contrast (2.85:1, needs 4.5:1) |\n| 5 | 4.1.2 | Major | Interactive element missing role and accessible name |\n\n```tsx\n\u002F\u002F AFTER: src\u002Fcomponents\u002FProductCard.tsx\nfunction ProductCard({ product }) {\n  return (\n    \u003Ca\n      href={`\u002Fproduct\u002F${product.id}`}\n      className=\"product-card\"\n      aria-label={`View ${product.name} - $${product.price}`}\n    >\n      \u003Cimg src={product.image} alt={product.imageAlt || product.name} \u002F>\n      \u003Cdiv style={{ color: '#595959', fontSize: '12px' }}>\n        {product.name}\n      \u003C\u002Fdiv>\n      \u003Cspan style={{ color: '#767676' }}>${product.price}\u003C\u002Fspan>\n    \u003C\u002Fa>\n  );\n}\n```\n\n**What changed:**\n- `\u003Cdiv onClick>` replaced with `\u003Ca href>` for native keyboard and screen reader support\n- `alt` attribute added to `\u003Cimg>` with meaningful fallback\n- `aria-label` provides full context for assistive technology\n- Color `#aaa` replaced with `#595959` (7.01:1 contrast ratio -- passes AA and AAA)\n- Color `#999` replaced with `#767676` (4.54:1 contrast ratio -- passes AA)\n\n### Example 2: Vue SFC Form Audit\n\n```vue\n\u003C!-- BEFORE: src\u002Fcomponents\u002FLoginForm.vue -->\n\u003Ctemplate>\n  \u003Cform @submit=\"handleLogin\">\n    \u003Cinput type=\"text\" placeholder=\"Email\" v-model=\"email\" \u002F>\n    \u003Cinput type=\"password\" placeholder=\"Password\" v-model=\"password\" \u002F>\n    \u003Cdiv v-if=\"error\" style=\"color: red\">{{ error }}\u003C\u002Fdiv>\n    \u003Cdiv @click=\"handleLogin\">Sign In\u003C\u002Fdiv>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n```\n\n**Violations detected:**\n\n| # | WCAG | Severity | Issue |\n|---|------|----------|-------|\n| 1 | 1.3.1 | Critical | Inputs missing associated `\u003Clabel>` elements |\n| 2 | 3.3.2 | Major | Placeholder text used as only label (disappears on input) |\n| 3 | 2.1.1 | Critical | `\u003Cdiv @click>` not keyboard accessible |\n| 4 | 4.1.3 | Major | Error message not announced to screen readers |\n| 5 | 3.3.1 | Major | Error not programmatically associated with input |\n\n```vue\n\u003C!-- AFTER: src\u002Fcomponents\u002FLoginForm.vue -->\n\u003Ctemplate>\n  \u003Cform @submit.prevent=\"handleLogin\" aria-label=\"Sign in to your account\">\n    \u003Cdiv class=\"field\">\n      \u003Clabel for=\"login-email\">Email\u003C\u002Flabel>\n      \u003Cinput\n        id=\"login-email\"\n        type=\"email\"\n        v-model=\"email\"\n        autocomplete=\"email\"\n        required\n        :aria-describedby=\"emailError ? 'email-error' : undefined\"\n        :aria-invalid=\"!!emailError\"\n      \u002F>\n      \u003Cspan v-if=\"emailError\" id=\"email-error\" role=\"alert\">\n        {{ emailError }}\n      \u003C\u002Fspan>\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"field\">\n      \u003Clabel for=\"login-password\">Password\u003C\u002Flabel>\n      \u003Cinput\n        id=\"login-password\"\n        type=\"password\"\n        v-model=\"password\"\n        autocomplete=\"current-password\"\n        required\n        :aria-describedby=\"passwordError ? 'password-error' : undefined\"\n        :aria-invalid=\"!!passwordError\"\n      \u002F>\n      \u003Cspan v-if=\"passwordError\" id=\"password-error\" role=\"alert\">\n        {{ passwordError }}\n      \u003C\u002Fspan>\n    \u003C\u002Fdiv>\n    \u003Cdiv v-if=\"error\" role=\"alert\" aria-live=\"assertive\" class=\"form-error\">\n      {{ error }}\n    \u003C\u002Fdiv>\n    \u003Cbutton type=\"submit\">Sign In\u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n```\n\n### Example 3: Angular Template Audit\n\n```html\n\u003C!-- BEFORE: src\u002Fapp\u002Fdashboard\u002Fdashboard.component.html -->\n\u003Cdiv class=\"tabs\">\n  \u003Cdiv *ngFor=\"let tab of tabs\"\n       (click)=\"selectTab(tab)\"\n       [class.active]=\"tab.active\">\n    {{ tab.label }}\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\u003Cdiv class=\"tab-content\">\n  \u003Cdiv *ngIf=\"selectedTab\">{{ selectedTab.content }}\u003C\u002Fdiv>\n\u003C\u002Fdiv>\n```\n\n**Violations detected:**\n\n| # | WCAG | Severity | Issue |\n|---|------|----------|-------|\n| 1 | 4.1.2 | Critical | Tab widget missing ARIA roles (`tablist`, `tab`, `tabpanel`) |\n| 2 | 2.1.1 | Critical | Tabs not keyboard navigable (arrow keys, Home, End) |\n| 3 | 2.4.11 | Major | No visible focus indicator on active tab |\n\n```html\n\u003C!-- AFTER: src\u002Fapp\u002Fdashboard\u002Fdashboard.component.html -->\n\u003Cdiv class=\"tabs\" role=\"tablist\" aria-label=\"Dashboard sections\">\n  \u003Cbutton\n    *ngFor=\"let tab of tabs; let i = index\"\n    role=\"tab\"\n    [id]=\"'tab-' + tab.id\"\n    [attr.aria-selected]=\"tab.active\"\n    [attr.aria-controls]=\"'panel-' + tab.id\"\n    [attr.tabindex]=\"tab.active ? 0 : -1\"\n    (click)=\"selectTab(tab)\"\n    (keydown)=\"handleTabKeydown($event, i)\"\n    class=\"tab-button\"\n    [class.active]=\"tab.active\">\n    {{ tab.label }}\n  \u003C\u002Fbutton>\n\u003C\u002Fdiv>\n\u003Cdiv\n  *ngIf=\"selectedTab\"\n  role=\"tabpanel\"\n  [id]=\"'panel-' + selectedTab.id\"\n  [attr.aria-labelledby]=\"'tab-' + selectedTab.id\"\n  tabindex=\"0\"\n  class=\"tab-content\">\n  {{ selectedTab.content }}\n\u003C\u002Fdiv>\n```\n\n**Supporting TypeScript for keyboard navigation:**\n\n```typescript\n\u002F\u002F dashboard.component.ts\nhandleTabKeydown(event: KeyboardEvent, index: number): void {\n  const tabCount = this.tabs.length;\n  let newIndex = index;\n\n  switch (event.key) {\n    case 'ArrowRight':\n      newIndex = (index + 1) % tabCount;\n      break;\n    case 'ArrowLeft':\n      newIndex = (index - 1 + tabCount) % tabCount;\n      break;\n    case 'Home':\n      newIndex = 0;\n      break;\n    case 'End':\n      newIndex = tabCount - 1;\n      break;\n    default:\n      return;\n  }\n\n  event.preventDefault();\n  this.selectTab(this.tabs[newIndex]);\n  \u002F\u002F Move focus to the new tab button\n  const tabElement = document.getElementById(`tab-${this.tabs[newIndex].id}`);\n  tabElement?.focus();\n}\n```\n\n### Example 4: Next.js Page-Level Audit\n\n```tsx\n\u002F\u002F BEFORE: src\u002Fapp\u002Fpage.tsx\nexport default function Home() {\n  return (\n    \u003Cmain>\n      \u003Cdiv className=\"text-4xl font-bold\">Welcome to Acme\u003C\u002Fdiv>\n      \u003Cdiv className=\"mt-4\">\n        Build better products with our platform.\n      \u003C\u002Fdiv>\n      \u003Cdiv className=\"mt-8 bg-blue-600 text-white px-6 py-3 rounded cursor-pointer\"\n           onClick={() => router.push('\u002Fsignup')}>\n        Get Started\n      \u003C\u002Fdiv>\n    \u003C\u002Fmain>\n  );\n}\n```\n\n**Violations detected:**\n\n| # | WCAG | Severity | Issue |\n|---|------|----------|-------|\n| 1 | 1.3.1 | Major | Heading uses `\u003Cdiv>` instead of `\u003Ch1>` -- no semantic structure |\n| 2 | 2.4.2 | Major | Page missing `\u003Ctitle>` (Next.js metadata) |\n| 3 | 2.1.1 | Critical | CTA uses `\u003Cdiv onClick>` -- not keyboard accessible |\n| 4 | 3.1.1 | Minor | `\u003Chtml>` missing `lang` attribute (check `layout.tsx`) |\n\n```tsx\n\u002F\u002F AFTER: src\u002Fapp\u002Fpage.tsx\nimport type { Metadata } from 'next';\nimport Link from 'next\u002Flink';\n\nexport const metadata: Metadata = {\n  title: 'Acme - Build Better Products',\n  description: 'Build better products with the Acme platform.',\n};\n\nexport default function Home() {\n  return (\n    \u003Cmain>\n      \u003Ch1 className=\"text-4xl font-bold\">Welcome to Acme\u003C\u002Fh1>\n      \u003Cp className=\"mt-4\">\n        Build better products with our platform.\n      \u003C\u002Fp>\n      \u003CLink\n        href=\"\u002Fsignup\"\n        className=\"mt-8 inline-block bg-blue-600 text-white px-6 py-3 rounded\n                   hover:bg-blue-700 focus-visible:outline focus-visible:outline-2\n                   focus-visible:outline-offset-2 focus-visible:outline-blue-600\"\n      >\n        Get Started\n      \u003C\u002FLink>\n    \u003C\u002Fmain>\n  );\n}\n```\n\n```tsx\n\u002F\u002F Also fix: src\u002Fapp\u002Flayout.tsx\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    \u003Chtml lang=\"en\">\n      \u003Cbody>{children}\u003C\u002Fbody>\n    \u003C\u002Fhtml>\n  );\n}\n```\n\n### Example 5: Svelte Component Audit\n\n```svelte\n\u003C!-- BEFORE: src\u002Flib\u002Fcomponents\u002FAccordion.svelte -->\n\u003Cscript>\n  let openIndex = -1;\n  function toggle(i) { openIndex = openIndex === i ? -1 : i; }\n\u003C\u002Fscript>\n\n{#each items as item, i}\n  \u003Cdiv class=\"header\" on:click={() => toggle(i)}>\n    {item.title}\n  \u003C\u002Fdiv>\n  {#if openIndex === i}\n    \u003Cdiv class=\"content\">{item.body}\u003C\u002Fdiv>\n  {\u002Fif}\n{\u002Feach}\n```\n\n**Violations detected:**\n\n| # | WCAG | Severity | Issue |\n|---|------|----------|-------|\n| 1 | 4.1.2 | Critical | Accordion missing ARIA roles and properties |\n| 2 | 2.1.1 | Critical | Headers not keyboard accessible |\n| 3 | 2.5.8 | Minor | Click targets may be smaller than 24x24px (NEW in WCAG 2.2) |\n\n```svelte\n\u003C!-- AFTER: src\u002Flib\u002Fcomponents\u002FAccordion.svelte -->\n\u003Cscript>\n  export let items = [];\n  let openIndex = -1;\n\n  function toggle(i) {\n    openIndex = openIndex === i ? -1 : i;\n  }\n\n  function handleKeydown(event, i) {\n    if (event.key === 'Enter' || event.key === ' ') {\n      event.preventDefault();\n      toggle(i);\n    }\n  }\n\u003C\u002Fscript>\n\n\u003Cdiv class=\"accordion\">\n  {#each items as item, i}\n    \u003Ch3>\n      \u003Cbutton\n        class=\"accordion-header\"\n        aria-expanded={openIndex === i}\n        aria-controls=\"panel-{i}\"\n        id=\"header-{i}\"\n        on:click={() => toggle(i)}\n        on:keydown={(e) => handleKeydown(e, i)}\n      >\n        {item.title}\n        \u003Cspan class=\"icon\" aria-hidden=\"true\">\n          {openIndex === i ? '−' : '+'}\n        \u003C\u002Fspan>\n      \u003C\u002Fbutton>\n    \u003C\u002Fh3>\n    \u003Cdiv\n      id=\"panel-{i}\"\n      role=\"region\"\n      aria-labelledby=\"header-{i}\"\n      class=\"accordion-content\"\n      class:open={openIndex === i}\n      hidden={openIndex !== i}\n    >\n      {item.body}\n    \u003C\u002Fdiv>\n  {\u002Feach}\n\u003C\u002Fdiv>\n\n\u003Cstyle>\n  .accordion-header {\n    min-height: 44px; \u002F* WCAG 2.5.8 Target Size *\u002F\n    width: 100%;\n    padding: 12px 16px;\n    cursor: pointer;\n    text-align: left;\n  }\n  .accordion-header:focus-visible {\n    outline: 2px solid #005fcc;\n    outline-offset: 2px;\n  }\n\u003C\u002Fstyle>\n```\n\n## Fix Patterns by Framework\n\n### React \u002F Next.js Fix Patterns\n\n#### Missing Alt Text (1.1.1)\n\n```tsx\n\u002F\u002F BEFORE\n\u003Cimg src={hero} \u002F>\n\n\u002F\u002F AFTER - Informational image\n\u003Cimg src={hero} alt=\"Team collaborating around a whiteboard\" \u002F>\n\n\u002F\u002F AFTER - Decorative image\n\u003Cimg src={divider} alt=\"\" role=\"presentation\" \u002F>\n```\n\n#### Non-Interactive Element with Click Handler (2.1.1)\n\n```tsx\n\u002F\u002F BEFORE\n\u003Cdiv onClick={handleClick}>Click me\u003C\u002Fdiv>\n\n\u002F\u002F AFTER - If it navigates\n\u003CLink href=\"\u002Fdestination\">Click me\u003C\u002FLink>\n\n\u002F\u002F AFTER - If it performs an action\n\u003Cbutton type=\"button\" onClick={handleClick}>Click me\u003C\u002Fbutton>\n```\n\n#### Missing Focus Management in Modals (2.4.3)\n\n```tsx\n\u002F\u002F BEFORE\nfunction Modal({ isOpen, onClose, children }) {\n  if (!isOpen) return null;\n  return \u003Cdiv className=\"modal-overlay\">{children}\u003C\u002Fdiv>;\n}\n\n\u002F\u002F AFTER\nimport { useEffect, useRef } from 'react';\n\nfunction Modal({ isOpen, onClose, children, title }) {\n  const modalRef = useRef(null);\n  const previousFocus = useRef(null);\n\n  useEffect(() => {\n    if (isOpen) {\n      previousFocus.current = document.activeElement;\n      modalRef.current?.focus();\n    } else {\n      previousFocus.current?.focus();\n    }\n  }, [isOpen]);\n\n  useEffect(() => {\n    if (!isOpen) return;\n    const handleKeydown = (e) => {\n      if (e.key === 'Escape') onClose();\n      if (e.key === 'Tab') {\n        const focusable = modalRef.current?.querySelectorAll(\n          'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n        );\n        if (!focusable?.length) return;\n        const first = focusable[0];\n        const last = focusable[focusable.length - 1];\n        if (e.shiftKey && document.activeElement === first) {\n          e.preventDefault();\n          last.focus();\n        } else if (!e.shiftKey && document.activeElement === last) {\n          e.preventDefault();\n          first.focus();\n        }\n      }\n    };\n    document.addEventListener('keydown', handleKeydown);\n    return () => document.removeEventListener('keydown', handleKeydown);\n  }, [isOpen, onClose]);\n\n  if (!isOpen) return null;\n\n  return (\n    \u003Cdiv className=\"modal-overlay\" onClick={onClose} aria-hidden=\"true\">\n      \u003Cdiv\n        ref={modalRef}\n        role=\"dialog\"\n        aria-modal=\"true\"\n        aria-label={title}\n        tabIndex={-1}\n        onClick={(e) => e.stopPropagation()}\n      >\n        \u003Cbutton\n          onClick={onClose}\n          aria-label=\"Close dialog\"\n          className=\"modal-close\"\n        >\n          &times;\n        \u003C\u002Fbutton>\n        {children}\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n#### Focus Appearance (2.4.11 -- NEW in WCAG 2.2)\n\n```css\n\u002F* BEFORE *\u002F\nbutton:focus {\n  outline: none; \u002F* Removes default focus indicator *\u002F\n}\n\n\u002F* AFTER - Meets WCAG 2.2 Focus Appearance *\u002F\nbutton:focus-visible {\n  outline: 2px solid #005fcc;\n  outline-offset: 2px;\n}\n```\n\n```tsx\n\u002F\u002F Tailwind CSS pattern\n\u003Cbutton className=\"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600\">\n  Submit\n\u003C\u002Fbutton>\n```\n\n### Vue Fix Patterns\n\n#### Missing Form Labels (1.3.1)\n\n```vue\n\u003C!-- BEFORE -->\n\u003Cinput type=\"text\" v-model=\"name\" placeholder=\"Name\" \u002F>\n\n\u003C!-- AFTER -->\n\u003Clabel for=\"user-name\">Name\u003C\u002Flabel>\n\u003Cinput id=\"user-name\" type=\"text\" v-model=\"name\" autocomplete=\"name\" \u002F>\n```\n\n#### Dynamic Content Without Live Region (4.1.3)\n\n```vue\n\u003C!-- BEFORE -->\n\u003Cdiv v-if=\"status\">{{ statusMessage }}\u003C\u002Fdiv>\n\n\u003C!-- AFTER -->\n\u003Cdiv aria-live=\"polite\" aria-atomic=\"true\">\n  \u003Cp v-if=\"status\">{{ statusMessage }}\u003C\u002Fp>\n\u003C\u002Fdiv>\n```\n\n#### Vue Router Navigation Announcements (2.4.2)\n\n```typescript\n\u002F\u002F router\u002Findex.ts\nrouter.afterEach((to) => {\n  const title = to.meta.title || 'Page';\n  document.title = `${title} | My App`;\n\n  \u002F\u002F Announce route change to screen readers\n  const announcer = document.getElementById('route-announcer');\n  if (announcer) {\n    announcer.textContent = `Navigated to ${title}`;\n  }\n});\n```\n\n```vue\n\u003C!-- App.vue - Add announcer element -->\n\u003Cdiv\n  id=\"route-announcer\"\n  role=\"status\"\n  aria-live=\"assertive\"\n  aria-atomic=\"true\"\n  class=\"sr-only\"\n>\u003C\u002Fdiv>\n```\n\n### Angular Fix Patterns\n\n#### Missing ARIA on Custom Components (4.1.2)\n\n```typescript\n\u002F\u002F BEFORE\n@Component({\n  selector: 'app-dropdown',\n  template: `\n    \u003Cdiv (click)=\"toggle()\">{{ selected }}\u003C\u002Fdiv>\n    \u003Cdiv *ngIf=\"isOpen\">\n      \u003Cdiv *ngFor=\"let opt of options\" (click)=\"select(opt)\">{{ opt }}\u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  `\n})\n\n\u002F\u002F AFTER\n@Component({\n  selector: 'app-dropdown',\n  template: `\n    \u003Cbutton\n      role=\"combobox\"\n      [attr.aria-expanded]=\"isOpen\"\n      aria-haspopup=\"listbox\"\n      [attr.aria-label]=\"label\"\n      (click)=\"toggle()\"\n      (keydown)=\"handleKeydown($event)\"\n    >\n      {{ selected }}\n    \u003C\u002Fbutton>\n    \u003Cul *ngIf=\"isOpen\" role=\"listbox\" [attr.aria-label]=\"label + ' options'\">\n      \u003Cli\n        *ngFor=\"let opt of options; let i = index\"\n        role=\"option\"\n        [attr.aria-selected]=\"opt === selected\"\n        [attr.id]=\"'option-' + i\"\n        (click)=\"select(opt)\"\n        (keydown)=\"handleOptionKeydown($event, opt, i)\"\n        tabindex=\"-1\"\n      >\n        {{ opt }}\n      \u003C\u002Fli>\n    \u003C\u002Ful>\n  `\n})\n```\n\n#### Angular CDK A11y Module Integration\n\n```typescript\n\u002F\u002F Use Angular CDK for focus trap in dialogs\nimport { A11yModule } from '@angular\u002Fcdk\u002Fa11y';\n\n@Component({\n  template: `\n    \u003Cdiv cdkTrapFocus cdkTrapFocusAutoCapture>\n      \u003Ch2 id=\"dialog-title\">Edit Profile\u003C\u002Fh2>\n      \u003C!-- dialog content -->\n    \u003C\u002Fdiv>\n  `\n})\n```\n\n### Svelte Fix Patterns\n\n#### Accessible Announcements (4.1.3)\n\n```svelte\n\u003C!-- BEFORE -->\n{#if message}\n  \u003Cp class=\"toast\">{message}\u003C\u002Fp>\n{\u002Fif}\n\n\u003C!-- AFTER -->\n\u003Cdiv aria-live=\"polite\" class=\"sr-only\">\n  {#if message}\n    \u003Cp>{message}\u003C\u002Fp>\n  {\u002Fif}\n\u003C\u002Fdiv>\n\u003Cdiv class=\"toast\" aria-hidden=\"true\">\n  {#if message}\n    \u003Cp>{message}\u003C\u002Fp>\n  {\u002Fif}\n\u003C\u002Fdiv>\n```\n\n#### SvelteKit Page Titles (2.4.2)\n\n```svelte\n\u003C!-- +page.svelte -->\n\u003Csvelte:head>\n  \u003Ctitle>Dashboard | My App\u003C\u002Ftitle>\n\u003C\u002Fsvelte:head>\n```\n\n### Plain HTML Fix Patterns\n\n#### Skip Navigation Link (2.4.1)\n\n```html\n\u003C!-- BEFORE -->\n\u003Cbody>\n  \u003Cnav>\u003C!-- long navigation -->\u003C\u002Fnav>\n  \u003Cmain>\u003C!-- content -->\u003C\u002Fmain>\n\u003C\u002Fbody>\n\n\u003C!-- AFTER -->\n\u003Cbody>\n  \u003Ca href=\"#main-content\" class=\"skip-link\">Skip to main content\u003C\u002Fa>\n  \u003Cnav aria-label=\"Main navigation\">\u003C!-- long navigation -->\u003C\u002Fnav>\n  \u003Cmain id=\"main-content\" tabindex=\"-1\">\u003C!-- content -->\u003C\u002Fmain>\n\u003C\u002Fbody>\n```\n\n```css\n.skip-link {\n  position: absolute;\n  top: -40px;\n  left: 0;\n  padding: 8px 16px;\n  background: #005fcc;\n  color: #fff;\n  z-index: 1000;\n  transition: top 0.2s;\n}\n.skip-link:focus {\n  top: 0;\n}\n```\n\n#### Accessible Data Table (1.3.1)\n\n```html\n\u003C!-- BEFORE -->\n\u003Ctable>\n  \u003Ctr>\u003Ctd>Name\u003C\u002Ftd>\u003Ctd>Email\u003C\u002Ftd>\u003Ctd>Role\u003C\u002Ftd>\u003C\u002Ftr>\n  \u003Ctr>\u003Ctd>Alice\u003C\u002Ftd>\u003Ctd>alice@co.com\u003C\u002Ftd>\u003Ctd>Admin\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftable>\n\n\u003C!-- AFTER -->\n\u003Ctable aria-label=\"Team members\">\n  \u003Ccaption class=\"sr-only\">List of team members and their roles\u003C\u002Fcaption>\n  \u003Cthead>\n    \u003Ctr>\n      \u003Cth scope=\"col\">Name\u003C\u002Fth>\n      \u003Cth scope=\"col\">Email\u003C\u002Fth>\n      \u003Cth scope=\"col\">Role\u003C\u002Fth>\n    \u003C\u002Ftr>\n  \u003C\u002Fthead>\n  \u003Ctbody>\n    \u003Ctr>\n      \u003Cth scope=\"row\">Alice\u003C\u002Fth>\n      \u003Ctd>alice@co.com\u003C\u002Ftd>\n      \u003Ctd>Admin\u003C\u002Ftd>\n    \u003C\u002Ftr>\n  \u003C\u002Ftbody>\n\u003C\u002Ftable>\n```\n\n## Color Contrast Checker\n\nThe `contrast_checker.py` script validates color pairs against WCAG 2.2 contrast requirements.\n\n### Usage\n\n```bash\n# Check a single color pair\npython scripts\u002Fcontrast_checker.py --fg \"#777777\" --bg \"#ffffff\"\n\n# Output:\n# Foreground: #777777 | Background: #ffffff\n# Contrast Ratio: 4.48:1\n# AA Normal Text (4.5:1): FAIL\n# AA Large Text (3.0:1):  PASS\n# AAA Normal Text (7.0:1): FAIL\n# Suggested alternative: #767676 (4.54:1 - passes AA)\n\n# Scan a CSS file for all color pairs\npython scripts\u002Fcontrast_checker.py --file src\u002Fstyles\u002Fglobals.css\n\n# Scan Tailwind classes in components\npython scripts\u002Fcontrast_checker.py --tailwind src\u002Fcomponents\u002F\n```\n\n### Common Contrast Fixes\n\n| Original Color | Contrast on White | Fix | New Contrast |\n|----------------|------------------|-----|--------------|\n| `#aaaaaa` | 2.32:1 | `#767676` | 4.54:1 (AA) |\n| `#999999` | 2.85:1 | `#767676` | 4.54:1 (AA) |\n| `#888888` | 3.54:1 | `#767676` | 4.54:1 (AA) |\n| `#777777` | 4.48:1 | `#757575` | 4.60:1 (AA) |\n| `#66bb6a` | 3.06:1 | `#2e7d32` | 5.87:1 (AA) |\n| `#42a5f5` | 2.81:1 | `#1565c0` | 6.08:1 (AA) |\n| `#ef5350` | 3.13:1 | `#c62828` | 5.57:1 (AA) |\n\n### Tailwind CSS Accessible Palette Mapping\n\n| Inaccessible Class | Contrast on White | Accessible Alternative | Contrast |\n|---------------------|------------------|----------------------|----------|\n| `text-gray-400` | 2.68:1 | `text-gray-600` | 5.74:1 |\n| `text-blue-400` | 2.81:1 | `text-blue-700` | 5.96:1 |\n| `text-green-400` | 2.12:1 | `text-green-700` | 5.18:1 |\n| `text-red-400` | 3.04:1 | `text-red-700` | 6.05:1 |\n| `text-yellow-500` | 1.47:1 | `text-yellow-800` | 7.34:1 |\n\n## CI\u002FCD Integration\n\n### GitHub Actions\n\n```yaml\n# .github\u002Fworkflows\u002Fa11y-audit.yml\nname: Accessibility Audit\n\non:\n  pull_request:\n    paths:\n      - 'src\u002F**\u002F*.tsx'\n      - 'src\u002F**\u002F*.vue'\n      - 'src\u002F**\u002F*.html'\n      - 'src\u002F**\u002F*.svelte'\n\njobs:\n  a11y-audit:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - name: Set up Python\n        uses: actions\u002Fsetup-python@v5\n        with:\n          python-version: '3.11'\n\n      - name: Run A11y Scanner\n        run: |\n          python scripts\u002Fa11y_scanner.py .\u002Fsrc --json > a11y-results.json\n\n      - name: Check for Critical Issues\n        run: |\n          python -c \"\n          import json, sys\n          with open('a11y-results.json') as f:\n              data = json.load(f)\n          critical = [v for v in data.get('violations', []) if v['severity'] == 'critical']\n          if critical:\n              print(f'FAILED: {len(critical)} critical a11y violations found')\n              for v in critical:\n                  print(f\\\"  [{v['wcag']}] {v['file']}:{v['line']} - {v['message']}\\\")\n              sys.exit(1)\n          print('PASSED: No critical a11y violations')\n          \"\n\n      - name: Upload Audit Report\n        if: always()\n        uses: actions\u002Fupload-artifact@v4\n        with:\n          name: a11y-audit-report\n          path: a11y-results.json\n\n      - name: Comment on PR\n        if: failure()\n        uses: marocchino\u002Fsticky-pull-request-comment@v2\n        with:\n          header: a11y-audit\n          message: |\n            ## Accessibility Audit Failed\n            Critical WCAG 2.2 violations were found. See the uploaded artifact for details.\n            Run `python scripts\u002Fa11y_scanner.py .\u002Fsrc` locally to view and fix issues.\n```\n\n### GitLab CI\n\n```yaml\n# .gitlab-ci.yml\na11y-audit:\n  stage: test\n  image: python:3.11-slim\n  script:\n    - python scripts\u002Fa11y_scanner.py .\u002Fsrc --json > a11y-results.json\n    - python -c \"\n      import json, sys;\n      data = json.load(open('a11y-results.json'));\n      critical = [v for v in data.get('violations', []) if v['severity'] == 'critical'];\n      sys.exit(1) if critical else print('A11y audit passed')\n      \"\n  artifacts:\n    paths:\n      - a11y-results.json\n    when: always\n  rules:\n    - changes:\n        - \"src\u002F**\u002F*.{tsx,vue,html,svelte}\"\n```\n\n### Azure DevOps\n\n```yaml\n# azure-pipelines.yml\n- task: PythonScript@0\n  displayName: 'Run A11y Audit'\n  inputs:\n    scriptSource: 'filePath'\n    scriptPath: 'scripts\u002Fa11y_scanner.py'\n    arguments: '.\u002Fsrc --json --output $(Build.ArtifactStagingDirectory)\u002Fa11y-results.json'\n\n- task: PublishBuildArtifacts@1\n  condition: always()\n  inputs:\n    PathtoPublish: '$(Build.ArtifactStagingDirectory)\u002Fa11y-results.json'\n    ArtifactName: 'a11y-audit-report'\n```\n\n### Pre-Commit Hook\n\n```bash\n#!\u002Fbin\u002Fbash\n# .git\u002Fhooks\u002Fpre-commit\n\n# Run a11y scan on staged files only\nSTAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\\.(tsx|vue|html|svelte|jsx)$')\n\nif [ -n \"$STAGED_FILES\" ]; then\n  echo \"Running accessibility audit on staged files...\"\n  for file in $STAGED_FILES; do\n    python scripts\u002Fa11y_scanner.py \"$file\" --severity critical --quiet\n    if [ $? -ne 0 ]; then\n      echo \"A11y audit FAILED for $file. Fix critical issues before committing.\"\n      exit 1\n    fi\n  done\n  echo \"A11y audit passed.\"\nfi\n```\n\n## Common Pitfalls\n\n| Pitfall | Why It Happens | Correct Approach |\n|---------|---------------|------------------|\n| Using `role=\"button\"` on a `\u003Cdiv>` | Developers think ARIA makes any element interactive | Use a native `\u003Cbutton>` element instead -- it includes keyboard handling, focus, and click events for free |\n| Setting `tabindex=\"0\"` on everything | Attempting to make elements focusable | Only interactive elements need focus. Use native elements (`\u003Ca>`, `\u003Cbutton>`, `\u003Cinput>`) which are focusable by default |\n| Using `aria-label` on non-interactive elements | Trying to add descriptions to `\u003Cdiv>` or `\u003Cspan>` | Screen readers may ignore `aria-label` on generic elements. Use `aria-labelledby` pointing to visible text, or restructure with headings |\n| Hiding content with `display: none` for screen readers | Wanting visual hiding | `display: none` hides from ALL users, including screen readers. Use `.sr-only` class for screen-reader-only content |\n| Using color alone to convey meaning | Red\u002Fgreen for status, error states | Add icons, text labels, or patterns alongside color. WCAG 1.4.1 requires non-color indicators |\n| Placeholder text as the only label | Saves visual space | Placeholder disappears on input, fails 1.3.1 and 3.3.2. Always provide a visible `\u003Clabel>` |\n| Auto-playing video or audio | Engagement metrics | Violates 1.4.2. Never autoplay media with sound. Provide pause\u002Fstop controls |\n| `outline: none` without replacement | Design preference | Violates 2.4.7 and 2.4.11. Always provide a visible focus indicator, use `focus-visible` to limit to keyboard users |\n| Empty `alt=\"\"` on informational images | Misunderstanding empty alt | Empty alt marks images as decorative. Informational images need descriptive alt text |\n| Missing heading hierarchy (h1 -> h3, skipping h2) | Visual styling drives heading choice | Heading levels must be sequential. Use CSS for styling, HTML for structure |\n| `onClick` without `onKeyDown` on custom elements | Mouse-first development | Custom interactive elements need keyboard support. Prefer native elements or add `onKeyDown` with Enter\u002FSpace handling |\n| Inaccessible custom `\u003Cselect>` replacements | Design requirements override native controls | Custom dropdowns need full ARIA: `combobox`, `listbox`, `option` roles, plus keyboard navigation (arrows, type-ahead, Escape) |\n| Ignoring `prefers-reduced-motion` | Animations assumed safe for all | Wrap animations in `@media (prefers-reduced-motion: no-preference)` or provide reduced alternatives |\n| CAPTCHAs without alternatives | Bot prevention | Violates 3.3.8 (WCAG 2.2). Provide alternative verification methods (email, SMS) alongside visual CAPTCHAs |\n\n## Screen Reader Utility Class\n\nEvery project should include this utility class for visually hiding content while keeping it accessible to screen readers:\n\n```css\n\u002F* Visually hidden but accessible to screen readers *\u002F\n.sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip: rect(0, 0, 0, 0);\n  white-space: nowrap;\n  border-width: 0;\n}\n\n\u002F* Allow the element to be focusable when navigated to via keyboard *\u002F\n.sr-only-focusable:focus,\n.sr-only-focusable:active {\n  position: static;\n  width: auto;\n  height: auto;\n  padding: inherit;\n  margin: inherit;\n  overflow: visible;\n  clip: auto;\n  white-space: inherit;\n}\n```\n\nTailwind CSS includes this as `sr-only` by default. For other frameworks:\n- **Angular**: Add to `styles.scss`\n- **Vue**: Add to `assets\u002Fglobal.css`\n- **Svelte**: Add to `app.css`\n\n## Audit Report Template\n\nThe scanner generates a stakeholder-ready report when run with the `--report` flag:\n\n```bash\npython scripts\u002Fa11y_scanner.py \u002Fpath\u002Fto\u002Fproject --report --output audit-report.md\n```\n\n**Generated report structure:**\n\n```markdown\n# Accessibility Audit Report\n**Project:** Acme Dashboard\n**Date:** 2026-03-18\n**Standard:** WCAG 2.2 Level AA\n**Tool:** a11y-audit v2.1.2\n\n## Executive Summary\n- Files Scanned: 127\n- Total Violations: 14\n- Critical: 3 | Major: 7 | Minor: 4\n- Estimated Remediation: 8-12 hours\n- Compliance Score: 72% (Target: 100%)\n\n## Violations by Category\n| Category | Count | Severity Breakdown |\n|----------|-------|--------------------|\n| Missing Alt Text | 3 | 2 Critical, 1 Minor |\n| Keyboard Access | 4 | 2 Critical, 2 Major |\n| Color Contrast | 3 | 3 Major |\n| Form Labels | 2 | 2 Major |\n| ARIA Usage | 2 | 2 Minor |\n\n## Detailed Findings\n[Per-violation details with file, line, WCAG criterion, and fix]\n\n## Remediation Priority\n1. Fix all Critical issues (blocks release)\n2. Fix Major issues in current sprint\n3. Schedule Minor issues for next sprint\n\n## Recommendations\n- Add a11y linting to CI pipeline (eslint-plugin-jsx-a11y)\n- Include keyboard testing in QA checklist\n- Schedule quarterly manual audit with assistive technology\n```\n\n## Tools Reference\n\n### a11y_scanner.py\n\nScans source files for WCAG 2.2 violations.\n\n```\nUsage: python scripts\u002Fa11y_scanner.py \u003Cpath> [options]\n\nArguments:\n  path                    File or directory to scan\n\nOptions:\n  --json                  Output results as JSON\n  --format {table,csv}    Output format (default: table)\n  --severity {critical,major,minor}\n                          Filter by minimum severity\n  --framework {react,vue,angular,svelte,html,auto}\n                          Force framework (default: auto-detect)\n  --baseline FILE         Compare against previous scan results\n  --report                Generate stakeholder report\n  --output FILE           Write results to file\n  --quiet                 Suppress output, exit code only\n  --ci                    CI mode: non-zero exit on critical issues\n```\n\n### contrast_checker.py\n\nValidates color contrast ratios against WCAG 2.2 requirements.\n\n```\nUsage: python scripts\u002Fcontrast_checker.py [options]\n\nOptions:\n  --fg COLOR              Foreground color (hex)\n  --bg COLOR              Background color (hex)\n  --file FILE             Scan CSS file for color pairs\n  --tailwind DIR          Scan directory for Tailwind color classes\n  --json                  Output results as JSON\n  --suggest               Suggest accessible alternatives for failures\n  --level {aa,aaa}        Target conformance level (default: aa)\n```\n\n## Testing Checklist\n\nUse this checklist after applying fixes to verify accessibility manually:\n\n### Keyboard Navigation\n- [ ] All interactive elements reachable via Tab key\n- [ ] Tab order follows visual\u002Flogical reading order\n- [ ] Focus indicator visible on every focusable element (2px+ outline)\n- [ ] Modals trap focus and return focus on close\n- [ ] Escape key closes modals, dropdowns, and popups\n- [ ] Arrow keys navigate within composite widgets (tabs, menus, listboxes)\n- [ ] No keyboard traps (user can always Tab away)\n\n### Screen Reader\n- [ ] All images have appropriate alt text (or `alt=\"\"` for decorative)\n- [ ] Headings create logical document outline (h1 -> h2 -> h3)\n- [ ] Form inputs have associated labels\n- [ ] Error messages announced via `aria-live` or `role=\"alert\"`\n- [ ] Page title updates on navigation (SPA)\n- [ ] Dynamic content changes announced appropriately\n\n### Visual\n- [ ] Text contrast meets 4.5:1 for normal text, 3:1 for large text\n- [ ] UI component contrast meets 3:1 against background\n- [ ] Content reflows without horizontal scrolling at 320px width\n- [ ] Text resizable to 200% without loss of content\n- [ ] No information conveyed by color alone\n- [ ] Focus indicators meet 2.4.11 Focus Appearance criteria\n\n### Motion and Media\n- [ ] Animations respect `prefers-reduced-motion`\n- [ ] No auto-playing media with audio\n- [ ] No content flashing more than 3 times per second\n- [ ] Video has captions; audio has transcripts\n\n### Forms\n- [ ] All inputs have visible labels\n- [ ] Required fields indicated (not by color alone)\n- [ ] Error messages specific and associated with input via `aria-describedby`\n- [ ] Autocomplete attributes present on common fields (name, email, etc.)\n- [ ] No CAPTCHA without alternative method (WCAG 2.2 3.3.8)\n\n## WCAG 2.2 New Success Criteria Reference\n\nThese criteria were added in WCAG 2.2 and are commonly missed:\n\n### 2.4.11 Focus Appearance (Level AA)\n\nThe focus indicator must have a minimum area of a 2px perimeter around the component and a contrast ratio of at least 3:1 against adjacent colors.\n\n**Pattern:**\n```css\n:focus-visible {\n  outline: 2px solid #005fcc;\n  outline-offset: 2px;\n}\n```\n\n### 2.5.7 Dragging Movements (Level AA)\n\nAny functionality that uses dragging must have a single-pointer alternative (click, tap).\n\n**Pattern:**\n```tsx\n\u002F\u002F Sortable list: support both drag and button-based reorder\n\u003Cli draggable onDragStart={handleDrag}>\n  {item.name}\n  \u003Cbutton onClick={() => moveUp(index)} aria-label={`Move ${item.name} up`}>\n    Move Up\n  \u003C\u002Fbutton>\n  \u003Cbutton onClick={() => moveDown(index)} aria-label={`Move ${item.name} down`}>\n    Move Down\n  \u003C\u002Fbutton>\n\u003C\u002Fli>\n```\n\n### 2.5.8 Target Size (Level AA)\n\nInteractive targets must be at least 24x24 CSS pixels, with exceptions for inline text links and elements where the spacing provides equivalent clearance.\n\n**Pattern:**\n```css\nbutton, a, input, select, textarea {\n  min-height: 24px;\n  min-width: 24px;\n}\n\n\u002F* Recommended: 44x44px for touch targets *\u002F\n@media (pointer: coarse) {\n  button, a, input[type=\"checkbox\"], input[type=\"radio\"] {\n    min-height: 44px;\n    min-width: 44px;\n  }\n}\n```\n\n### 3.3.7 Redundant Entry (Level A)\n\nInformation previously entered by the user must be auto-populated or available for selection when needed again in the same process.\n\n**Pattern:**\n```tsx\n\u002F\u002F Multi-step form: persist data across steps\nconst [formData, setFormData] = useState({});\n\n\u002F\u002F Step 2 pre-fills shipping address from billing\n\u003Cinput\n  defaultValue={formData.billingAddress || ''}\n  autoComplete=\"shipping street-address\"\n\u002F>\n```\n\n### 3.3.8 Accessible Authentication (Level AA)\n\nAuthentication must not require cognitive function tests (e.g., remembering a password, solving a puzzle) unless an alternative is provided.\n\n**Pattern:**\n- Support password managers (`autocomplete=\"current-password\"`)\n- Offer passkey \u002F biometric authentication\n- Allow copy-paste in password fields (never block paste)\n- Provide email\u002FSMS OTP as alternative to CAPTCHA\n\n## Related Skills\n\n| Skill | Relationship | Path |\n|-------|-------------|------|\n| **senior-frontend** | Frontend patterns used in a11y fixes (React, Next.js, Tailwind) | `engineering-team\u002Fsenior-frontend\u002F` |\n| **code-reviewer** | Include a11y checks in code review workflows | `engineering-team\u002Fcode-reviewer\u002F` |\n| **senior-qa** | Integration of a11y testing into QA processes | `engineering-team\u002Fsenior-qa\u002F` |\n| **playwright-pro** | Automated browser testing with accessibility assertions | `engineering-team\u002Fplaywright-pro\u002F` |\n| **senior-secops** | Accessibility as part of compliance and security posture | `engineering-team\u002Fsenior-secops\u002F` |\n| **epic-design** | WCAG 2.1 AA compliant animations and scroll storytelling | `engineering-team\u002Fepic-design\u002F` |\n| **tdd-guide** | Test-driven development patterns for a11y test cases | `engineering-team\u002Ftdd-guide\u002F` |\n| **incident-commander** | Respond to a11y compliance incidents and legal risk | `engineering-team\u002Fincident-commander\u002F` |\n\n## Resources\n\n- [WCAG 2.2 Specification](https:\u002F\u002Fwww.w3.org\u002FTR\u002FWCAG22\u002F)\n- [WAI-ARIA Authoring Practices 1.2](https:\u002F\u002Fwww.w3.org\u002FWAI\u002FARIA\u002Fapg\u002F)\n- [Deque axe-core Rules](https:\u002F\u002Fgithub.com\u002Fdequelabs\u002Faxe-core\u002Fblob\u002Fdevelop\u002Fdoc\u002Frule-descriptions.md)\n- [eslint-plugin-jsx-a11y](https:\u002F\u002Fgithub.com\u002Fjsx-eslint\u002Feslint-plugin-jsx-a11y)\n- [vue-a11y](https:\u002F\u002Fvue-a11y.com\u002F)\n- [@angular-eslint\u002Ftemplate-accessibility](https:\u002F\u002Fgithub.com\u002Fangular-eslint\u002Fangular-eslint)\n\n---\n\n**License:** MIT\n**Author:** Alireza Rezvani\n**Version:** 2.1.2\n\n",{"Name":5,"Tier":55,"name":5,"Author":56,"Version":57,"license":15,"Category":47,"metadata":58,"description":7,"Dependencies":60},"STANDARD","Alireza Rezvani","2.1.2",{"updated":59},1773792000000,"none","main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Falirezarezvani\u002Fclaude-skills\u002Fa11y-audit",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":71,"prompt":81,"zipAvailable":82},[72,75,78],{"tool":73,"command":74},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill a11y-audit",{"tool":76,"command":77},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill a11y-audit",{"tool":79,"command":80},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill a11y-audit","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Falirezarezvani\u002Fclaude-skills\u002Fa11y-audit\u002FSKILL.md",true,[],[85,95,105,114,120,127],{"skillNo":86,"name":87,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":88,"categories":89,"keywords":91,"publishedAt":50},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[90],{"categoryNo":27,"categoryName":28,"taxonomyType":21,"taxonomyTypeDesc":22},[92,93,94],"model","model hierarchy","model hierarchy skill",{"skillNo":96,"name":97,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":98,"categories":99,"keywords":101,"publishedAt":50},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[100],{"categoryNo":27,"categoryName":28,"taxonomyType":21,"taxonomyTypeDesc":22},[102,103,104],"MCP","zoom","zoom mcp",{"skillNo":106,"name":107,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":108,"categories":109,"keywords":110,"publishedAt":50},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[111,112,113,103],"sdk","zoom apps sdk","apps",{"skillNo":115,"name":116,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":117,"categories":118,"keywords":119,"publishedAt":50},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[116],{"skillNo":121,"name":122,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":123,"categories":124,"keywords":126,"publishedAt":50},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[125],{"categoryNo":27,"categoryName":28,"taxonomyType":21,"taxonomyTypeDesc":22},[122],{"skillNo":128,"name":129,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":130,"categories":131,"keywords":132,"publishedAt":50},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[129]]