diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index f2edb9b..3a03ff4 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -82,7 +82,7 @@ { "name": "engineering-skills", "source": "./engineering-team", - "description": "25 engineering skills: architecture, frontend, backend, fullstack, QA, DevOps, security, AI/ML, data engineering, Playwright (9 sub-skills), self-improving agent, Stripe integration, TDD guide, tech stack evaluator, Google Workspace CLI.", + "description": "26 engineering skills: architecture, frontend, backend, fullstack, QA, DevOps, security, AI/ML, data engineering, Playwright (9 sub-skills), self-improving agent, Stripe integration, TDD guide, tech stack evaluator, Google Workspace CLI, a11y audit (WCAG 2.2).", "version": "2.1.2", "author": { "name": "Alireza Rezvani" diff --git a/.claude/commands/seo-auditor.md b/.claude/commands/seo-auditor.md new file mode 100644 index 0000000..a740f0f --- /dev/null +++ b/.claude/commands/seo-auditor.md @@ -0,0 +1,97 @@ +--- +description: Scan and optimize docs for SEO — meta tags, readability, keywords, broken links, sitemap. +--- + +Run the SEO auditor on documentation files. Target path: `$ARGUMENTS` (default: all docs/ and root README.md). + +If `$ARGUMENTS` is `--report-only`, scan without making changes. + +Execute all 7 phases. Auto-fix non-destructive issues. Never change URLs. Preserve content on high-ranking pages. + +## Phase 1: Discovery + +Find all target markdown files: +- `docs/**/*.md` — all documentation pages +- `README.md` files in domain root directories +- If `$ARGUMENTS` specifies a path, scope to that path only + +For each file, extract current state: `title:` frontmatter, `description:` frontmatter, H1, H2s, word count, link count. Store as baseline for the report. + +Identify recently changed files: `git log --oneline -2 --name-only -- docs/ README.md` + +## Phase 2: Meta Tags + +For each file with YAML frontmatter: + +**Title** (`title:` field): +- Must be 50-60 characters +- Must contain a primary keyword +- Must be unique across all pages +- Auto-fix generic titles using domain context + +**Description** (`description:` field): +- Must be 120-160 characters +- Must contain primary keyword +- Must be unique — no duplicates +- Auto-fix from SKILL.md frontmatter or first paragraph + +Run SEO checker on built HTML pages: +```bash +python3 marketing-skill/seo-audit/scripts/seo_checker.py --file site/{path}/index.html +``` + +## Phase 3: Content Quality + +**Heading structure:** One H1 per page, no skipped levels, keywords in headings. + +**Readability:** Run content scorer: +```bash +python3 marketing-skill/content-production/scripts/content_scorer.py {file} +``` +Target: readability ≥ 70, structure ≥ 60. + +**AI detection** (on non-generated files only): +```bash +python3 marketing-skill/content-humanizer/scripts/humanizer_scorer.py {file} +``` +Flag pages < 50. Fix AI clichés: "delve", "leverage", "it's important to note", "comprehensive". + +**Do NOT rewrite** pages ranking well — only fix critical issues on those. + +## Phase 4: Keywords + +Check each page has its primary keyword in: title, description, H1, first paragraph, at least one H2. + +Keyword density: 1-2% for primary. Flag and reduce if > 3%. + +**Never change existing URLs.** Only optimize content and meta tags. + +## Phase 5: Links + +**Internal links:** Verify all `[text](url)` targets exist. Fix broken links. + +**Duplicate content:** +```bash +grep -rh '^description:' docs/**/*.md | sort | uniq -d +``` +Make each duplicate unique. + +**Orphan pages:** Find pages not in `mkdocs.yml` nav. Add them. + +## Phase 6: Sitemap + +Rebuild the site to regenerate sitemap: +```bash +mkdocs build +``` + +Analyze the sitemap: +```bash +python3 marketing-skill/site-architecture/scripts/sitemap_analyzer.py site/sitemap.xml +``` + +Verify all pages appear, no duplicates, no broken URLs. + +## Phase 7: Report + +Present a summary showing: pages scanned, issues found, auto-fixes applied, manual review items, broken links fixed, orphans resolved, sitemap URL count. List preserved pages that were not modified. diff --git a/.gemini/skills-index.json b/.gemini/skills-index.json index 553a6e6..487e9d4 100644 --- a/.gemini/skills-index.json +++ b/.gemini/skills-index.json @@ -1,7 +1,7 @@ { "version": "1.0.0", "name": "gemini-cli-skills", - "total_skills": 249, + "total_skills": 251, "skills": [ { "name": "README", @@ -328,6 +328,11 @@ "category": "command", "description": "Generate changelogs from git history and validate conventional commits. Usage: /changelog [options]" }, + { + "name": "cmd-a11y-audit", + "category": "command", + "description": "Scan a frontend project for WCAG 2.2 accessibility violations and fix them. Usage: /a11y-audit [path]" + }, { "name": "cmd-code-to-prd", "category": "command", @@ -418,6 +423,11 @@ "category": "command", "description": "Generate user stories with acceptance criteria and sprint planning. Usage: /user-story [options]" }, + { + "name": "a11y-audit", + "category": "engineering", + "description": "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/CD pipelines." + }, { "name": "aws-solution-architect", "category": "engineering", @@ -1263,11 +1273,11 @@ "description": "C-level resources" }, "command": { - "count": 19, + "count": 20, "description": "Command resources" }, "engineering": { - "count": 40, + "count": 41, "description": "Engineering resources" }, "engineering-advanced": { diff --git a/.gemini/skills/a11y-audit/SKILL.md b/.gemini/skills/a11y-audit/SKILL.md new file mode 120000 index 0000000..54d3cc2 --- /dev/null +++ b/.gemini/skills/a11y-audit/SKILL.md @@ -0,0 +1 @@ +../../../engineering-team/a11y-audit/SKILL.md \ No newline at end of file diff --git a/.gemini/skills/cmd-a11y-audit/SKILL.md b/.gemini/skills/cmd-a11y-audit/SKILL.md new file mode 120000 index 0000000..e1b9263 --- /dev/null +++ b/.gemini/skills/cmd-a11y-audit/SKILL.md @@ -0,0 +1 @@ +../../../commands/a11y-audit.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 3c9b19b..cedfa50 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,7 +38,7 @@ claude-code-skills/ ├── .claude-plugin/ # Plugin registry (marketplace.json) ├── agents/ # 16 cs-* prefixed agents across all domains ├── commands/ # 19 slash commands (changelog, tdd, saas-health, prd, code-to-prd, plugin-audit, sprint-plan, etc.) -├── engineering-team/ # 25 core engineering skills + Playwright Pro + Self-Improving Agent +├── engineering-team/ # 26 core engineering skills + Playwright Pro + Self-Improving Agent + A11y Audit ├── engineering/ # 30 POWERFUL-tier advanced skills (incl. AgentHub) ├── product-team/ # 13 product skills + Python tools ├── marketing-skill/ # 43 marketing skills (7 pods) + Python tools @@ -149,7 +149,7 @@ See [standards/git/git-workflow-standards.md](standards/git/git-workflow-standar ## Roadmap **Phase 1-2 Complete:** 204 production-ready skills deployed across 9 domains -- Engineering Core (25), Engineering POWERFUL (30), Product (14), Marketing (43), PM (6), C-Level (28), RA/QM (12), Business & Growth (4), Finance (2) +- Engineering Core (26), Engineering POWERFUL (30), Product (14), Marketing (43), PM (6), C-Level (28), RA/QM (12), Business & Growth (4), Finance (2) - 268 Python automation tools, 384 reference guides, 16 agents, 19 commands - Complete enterprise coverage from engineering through regulatory compliance, sales, customer success, and finance - MkDocs Material docs site with 210+ indexed pages for SEO @@ -203,4 +203,4 @@ This repository publishes skills to **ClawHub** (clawhub.com) as the distributio **Last Updated:** March 11, 2026 **Version:** v2.1.2 -**Status:** 205 skills deployed across 9 domains, 22 marketplace plugins, docs site live +**Status:** 205 skills deployed across 9 domains, 28 marketplace plugins, docs site live diff --git a/README.md b/README.md index a80606e..970a068 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ See [integrations/](integrations/) for tool-specific documentation and pre-gener | Domain | Skills | Highlights | Details | |--------|--------|------------|---------| -| **🔧 Engineering — Core** | 25 | Architecture, frontend, backend, fullstack, QA, DevOps, SecOps, AI/ML, data, Playwright, self-improving agent, Google Workspace CLI | [engineering-team/](engineering-team/) | +| **🔧 Engineering — Core** | 26 | Architecture, frontend, backend, fullstack, QA, DevOps, SecOps, AI/ML, data, Playwright, self-improving agent, Google Workspace CLI, a11y audit | [engineering-team/](engineering-team/) | | **🎭 Playwright Pro** | 9+3 | Test generation, flaky fix, Cypress/Selenium migration, TestRail, BrowserStack, 55 templates | [engineering-team/playwright-pro](engineering-team/playwright-pro/) | | **🧠 Self-Improving Agent** | 5+2 | Auto-memory curation, pattern promotion, skill extraction, memory health | [engineering-team/self-improving-agent](engineering-team/self-improving-agent/) | | **⚡ Engineering — POWERFUL** | 30 | Agent designer, RAG architect, database designer, CI/CD builder, security auditor, MCP builder, AgentHub, Helm charts, Terraform | [engineering/](engineering/) | diff --git a/commands/seo-auditor.md b/commands/seo-auditor.md new file mode 100644 index 0000000..167a304 --- /dev/null +++ b/commands/seo-auditor.md @@ -0,0 +1,340 @@ +--- +name: seo-auditor +description: | + Scan and optimize documentation files for SEO. Audits README.md files and docs/ pages for + meta tags, headings, keywords, readability, duplicate content, and broken links. Applies + fixes, updates sitemap.xml, and generates a report. Usage: /seo-auditor [path] +--- + +# /seo-auditor + +Systematically scan, audit, and optimize documentation files for SEO. Targets README.md files and docs/ pages — fixes issues in place, preserves rankings on high-performing pages, and generates a final report. + +## Usage + +```bash +/seo-auditor # Audit all docs/ and root README.md +/seo-auditor docs/skills/ # Audit a specific docs subdirectory +/seo-auditor --report-only # Scan without making changes +``` + +## What It Does + +Execute all 7 phases sequentially. Auto-fix non-destructive issues. Preserve existing high-ranking content. Report everything at the end. + +--- + +## Phase 1: Discovery & Baseline + +### 1a. Identify target files + +Scan for documentation files that need SEO audit: + +```bash +# Find all markdown files in docs/ and root README files +find docs/ -name '*.md' -type f | sort +find . -maxdepth 2 -name 'README.md' -not -path './.codex/*' -not -path './.gemini/*' | sort +``` + +Classify each file: +- **New/recently modified** — files changed in the last 2 commits (check via `git log`) +- **Index pages** — `index.md` files (high authority, handle with care) +- **Skill pages** — `docs/skills/**/*.md` (generated by `generate-docs.py`) +- **Static pages** — `docs/index.md`, `docs/getting-started.md`, `docs/integrations.md`, etc. +- **README files** — root and domain-level README.md + +### 1b. Capture baseline + +For each target file, extract current SEO state: +- `title:` frontmatter field → becomes `` tag +- `description:` frontmatter field → becomes `<meta name="description">` +- First `# H1` heading +- All `## H2` and `### H3` subheadings +- Word count +- Internal link count +- External link count + +Store baseline in memory for the report. + +--- + +## Phase 2: Meta Tag Audit + +For every file with YAML frontmatter, check and fix: + +### Title Tag (`title:`) + +**Rules:** +- Must exist and be non-empty +- Length: 50-60 characters ideal (Google truncates at ~60) +- Must contain a primary keyword +- Must NOT duplicate another page's title +- For skill pages: should follow the pattern `{Skill Name} — {Differentiator} - {site_name}` +- site_name from `mkdocs.yml` is appended automatically — don't duplicate it in the title + +**Auto-fix:** If title is generic (e.g., just the skill name), enrich it with domain context using the DOMAIN_SEO_SUFFIX pattern from `scripts/generate-docs.py`. + +### Meta Description (`description:`) + +**Rules:** +- Must exist and be non-empty +- Length: 120-160 characters (Google truncates at ~160) +- Must contain the primary keyword naturally +- Must be unique across all pages — no two pages share the same description +- Should include a call-to-action or value proposition +- Must NOT start with "This page..." or "This document..." + +**Auto-fix:** If description is missing or generic, generate one from the SKILL.md frontmatter description (if available) or from the first paragraph of content. Use the `extract_description_from_frontmatter()` function from `generate-docs.py` as reference. + +### Validation Script + +Run on each file that has HTML output in `site/`: + +```bash +python3 marketing-skill/seo-audit/scripts/seo_checker.py --file site/{path}/index.html +``` + +Parse the score. Flag any page scoring below 60. + +--- + +## Phase 3: Content Quality & Readability + +For each target file, analyze and improve: + +### Heading Structure + +**Rules:** +- Exactly one `# H1` per page +- H2s follow H1, H3s follow H2 — no skipping levels +- Headings should contain keywords naturally (not stuffed) +- No duplicate headings on the same page + +**Auto-fix:** If heading levels skip (H1 → H3), adjust to proper hierarchy. + +### Readability + +Run the content scorer on each file: + +```bash +python3 marketing-skill/content-production/scripts/content_scorer.py {file_path} +``` + +Check scores for: +- **Readability** — aim for score ≥ 70 +- **Structure** — aim for score ≥ 60 +- **Engagement** — aim for score ≥ 50 + +### Content Quality Rules + +- **Paragraphs:** No single paragraph longer than 5 sentences +- **Sentences:** Average sentence length 15-20 words +- **Passive voice:** Less than 15% of sentences +- **Transition words:** At least 30% of sentences use transitions +- **Bullet lists:** Use lists for 3+ items instead of comma-separated inline lists + +### AI Content Detection + +Run the humanizer scorer on non-generated content (README.md files, static pages): + +```bash +python3 marketing-skill/content-humanizer/scripts/humanizer_scorer.py {file_path} +``` + +Flag pages scoring below 50 (too AI-sounding). For these pages, apply voice techniques from `marketing-skill/content-humanizer/references/voice-techniques.md`: +- Replace AI clichés ("delve into", "leverage", "it's important to note") +- Vary sentence length +- Add specific examples instead of generic statements +- Use active voice + +**Important:** Only modify content that was recently created or updated. Do NOT rewrite pages that are ranking well — preserve their content. + +--- + +## Phase 4: Keyword Optimization + +### 4a. Identify target keywords per page + +Based on the page's purpose and domain: + +| Page Type | Primary Keywords | Secondary Keywords | +|-----------|-----------------|-------------------| +| Homepage (docs/index.md) | "Claude Code Skills", "agent plugins" | "Codex skills", "Gemini CLI", "OpenClaw" | +| Skill pages | Skill name + "Claude Code" | "agent skill", "Codex plugin", domain terms | +| Agent pages | Agent name + "AI coding agent" | "Claude Code", "orchestrator" | +| Command pages | Command name + "slash command" | "Claude Code", "AI coding" | +| Getting started | "install Claude Code skills" | platform names | +| Domain index | Domain + "skills" + "plugins" | "Claude Code", platform names | + +### 4b. Keyword placement checks + +For each page, verify the primary keyword appears in: +- [ ] Title tag (frontmatter `title:`) +- [ ] Meta description (frontmatter `description:`) +- [ ] H1 heading +- [ ] First paragraph (within first 100 words) +- [ ] At least one H2 subheading +- [ ] Image alt text (if images present) +- [ ] URL slug (for new pages only — never change existing URLs) + +### 4c. Keyword density + +- Primary keyword: 1-2% of total word count +- Secondary keywords: 0.5-1% each +- No keyword stuffing — if density exceeds 3%, reduce it + +**Important:** Never change URLs of existing pages. URL changes break incoming links and destroy rankings. Only optimize content and meta tags. + +--- + +## Phase 5: Link Audit + +### 5a. Internal links + +For each target file, check all markdown links `[text](url)`: + +- Verify the target exists (file path resolves) +- Check for broken relative links (`../`, `./`) +- Verify anchor links (`#section-name`) point to existing headings + +**Auto-fix:** Use the `rewrite_skill_internal_links()` and `rewrite_relative_links()` functions from `generate-docs.py` as reference. Rewrite broken skill-internal links to GitHub source URLs. + +### 5b. Duplicate content detection + +Compare meta descriptions across all pages: + +```bash +grep -rh '^description:' docs/**/*.md | sort | uniq -d +``` + +If duplicates found, make each description unique by adding page-specific context. + +Compare H1 headings across all pages — no two pages should have the same H1. + +### 5c. Orphan page detection + +Check if every page in `docs/` is referenced in `mkdocs.yml` nav. Pages not in nav are orphans — they won't appear in navigation and may not be indexed. + +```bash +# Find doc pages not in mkdocs nav +find docs -name '*.md' -not -name 'index.md' | while read f; do + slug=$(echo "$f" | sed 's|docs/||') + grep -q "$slug" mkdocs.yml || echo "ORPHAN: $f" +done +``` + +**Auto-fix:** Add orphan pages to the correct nav section in `mkdocs.yml`. + +--- + +## Phase 6: Sitemap & Build + +### 6a. Rebuild the site + +```bash +mkdocs build +``` + +This regenerates `site/sitemap.xml` automatically (MkDocs Material generates it during build). + +### 6b. Verify sitemap + +Check the generated sitemap: + +```bash +python3 marketing-skill/site-architecture/scripts/sitemap_analyzer.py site/sitemap.xml +``` + +Verify: +- All documentation pages appear in the sitemap +- No broken/404 URLs +- URL count matches expected page count +- Depth distribution is reasonable (no pages deeper than 4 levels) + +### 6c. Check for sitemap issues + +- **Missing pages:** Pages in `mkdocs.yml` nav that don't appear in sitemap +- **Extra pages:** Pages in sitemap that aren't in nav (orphans) +- **Duplicate URLs:** Same page accessible via multiple URLs + +--- + +## Phase 7: Report + +Generate a concise report for the user: + +``` +╔══════════════════════════════════════════════════════════════╗ +║ SEO AUDITOR REPORT ║ +╠══════════════════════════════════════════════════════════════╣ +║ ║ +║ Pages scanned: {n} ║ +║ Issues found: {n} ║ +║ Auto-fixed: {n} ║ +║ Manual review needed: {n} ║ +║ ║ +║ META TAGS ║ +║ Titles optimized: {n} ║ +║ Descriptions fixed: {n} ║ +║ Duplicate titles: {n} → {n} (fixed) ║ +║ Duplicate descs: {n} → {n} (fixed) ║ +║ ║ +║ CONTENT ║ +║ Readability improved: {n} pages ║ +║ Heading fixes: {n} ║ +║ AI score improved: {n} pages ║ +║ ║ +║ KEYWORDS ║ +║ Pages missing primary keyword in title: {n} ║ +║ Pages missing keyword in description: {n} ║ +║ Pages with keyword stuffing: {n} ║ +║ ║ +║ LINKS ║ +║ Broken links found: {n} → {n} (fixed) ║ +║ Orphan pages: {n} → {n} (added to nav) ║ +║ Duplicate content: {n} → {n} (deduplicated) ║ +║ ║ +║ SITEMAP ║ +║ Total URLs: {n} ║ +║ Sitemap regenerated: ✅ ║ +║ ║ +║ PRESERVED (no changes — ranking well) ║ +║ {list of pages left untouched} ║ +║ ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +### Pages to preserve (do NOT modify) + +These pages rank well for their target keywords. Only fix critical issues (broken links, missing meta). Do NOT rewrite content: + +- `docs/index.md` — homepage, ranks for "Claude Code Skills" +- `docs/getting-started.md` — installation guide +- `docs/integrations.md` — multi-tool support +- Any page the user explicitly marks as "preserve" + +--- + +## Skill References + +| Tool | Path | Use | +|------|------|-----| +| SEO Checker | `marketing-skill/seo-audit/scripts/seo_checker.py` | Score HTML pages 0-100 | +| Content Scorer | `marketing-skill/content-production/scripts/content_scorer.py` | Score content readability/structure/engagement | +| Humanizer Scorer | `marketing-skill/content-humanizer/scripts/humanizer_scorer.py` | Detect AI-sounding content | +| Headline Scorer | `marketing-skill/copywriting/scripts/headline_scorer.py` | Score title quality | +| SEO Optimizer | `marketing-skill/content-production/scripts/seo_optimizer.py` | Optimize content for target keyword | +| Sitemap Analyzer | `marketing-skill/site-architecture/scripts/sitemap_analyzer.py` | Analyze sitemap structure | +| Schema Validator | `marketing-skill/schema-markup/scripts/schema_validator.py` | Validate structured data | +| Topic Cluster Mapper | `marketing-skill/content-strategy/scripts/topic_cluster_mapper.py` | Group pages into content clusters | + +### Reference Docs + +| Reference | Path | Use | +|-----------|------|-----| +| SEO Audit Framework | `marketing-skill/seo-audit/references/seo-audit-reference.md` | Priority order for SEO fixes | +| AI Search Optimization | `marketing-skill/ai-seo/references/content-patterns.md` | Make content citable by AI | +| Content Optimization | `marketing-skill/content-production/references/optimization-checklist.md` | Pre-publish checklist | +| URL Design Guide | `marketing-skill/site-architecture/references/url-design-guide.md` | URL structure best practices | +| Internal Linking | `marketing-skill/site-architecture/references/internal-linking-playbook.md` | Internal linking strategy | +| AI Writing Detection | `marketing-skill/content-humanizer/references/ai-tells-checklist.md` | AI cliché removal | diff --git a/docs/commands/a11y-audit.md b/docs/commands/a11y-audit.md new file mode 100644 index 0000000..76342f6 --- /dev/null +++ b/docs/commands/a11y-audit.md @@ -0,0 +1,90 @@ +--- +title: "/a11y-audit — Slash Command for AI Coding Agents" +description: "Scan a frontend project for WCAG 2.2 accessibility violations and fix them. Usage: /a11y-audit [path]. Slash command for Claude Code, Codex CLI, Gemini CLI." +--- + +# /a11y-audit + +<div class="page-meta" markdown> +<span class="meta-badge">:material-console: Slash Command</span> +<span class="meta-badge">:material-github: <a href="https://github.com/alirezarezvani/claude-skills/tree/main/commands/a11y-audit.md">Source</a></span> +</div> + + +Scan a frontend project for WCAG 2.2 accessibility issues, show fixes, and optionally check color contrast. + +## Usage + +```bash +/a11y-audit # Scan current project +/a11y-audit ./src # Scan specific directory +/a11y-audit ./src --fix # Scan and auto-fix what's possible +``` + +## What It Does + +### Step 1: Scan + +Run the a11y scanner on the target directory: + +```bash +python3 {skill_path}/scripts/a11y_scanner.py {path} --json +``` + +Parse the JSON output. Group findings by severity (critical → serious → moderate → minor). + +Display a summary: +``` +A11y Audit: ./src + Critical: 3 | Serious: 7 | Moderate: 12 | Minor: 5 + Files scanned: 42 | Files with issues: 15 +``` + +### Step 2: Fix + +For each finding (starting with critical): + +1. Read the affected file +2. Show the violation with context (before) +3. Apply the fix from `references/framework-a11y-patterns.md` +4. Show the result (after) + +**Auto-fixable issues** (apply without asking): +- Missing `alt=""` on decorative images +- Missing `lang` attribute on `<html>` +- `tabindex` values > 0 → set to 0 +- Missing `type="button"` on non-submit buttons +- Outline removal without replacement → add `:focus-visible` styles + +**Issues requiring user input** (show fix, ask to apply): +- Missing alt text (need description from user) +- Missing form labels (need label text) +- Heading restructuring (may affect layout) +- ARIA role changes (may affect functionality) + +### Step 3: Contrast Check + +If CSS files are present, run the contrast checker: + +```bash +python3 {skill_path}/scripts/contrast_checker.py --batch {path} +``` + +For each failing color pair, suggest accessible alternatives. + +### Step 4: Report + +Generate a markdown report at `a11y-report.md`: +- Executive summary (pass/fail, issue counts) +- Per-file findings with before/after diffs +- Remaining manual review items +- WCAG criteria coverage + +## Skill Reference + +- `engineering-team/a11y-audit/SKILL.md` +- `engineering-team/a11y-audit/scripts/a11y_scanner.py` +- `engineering-team/a11y-audit/scripts/contrast_checker.py` +- `engineering-team/a11y-audit/references/wcag-quick-ref.md` +- `engineering-team/a11y-audit/references/aria-patterns.md` +- `engineering-team/a11y-audit/references/framework-a11y-patterns.md` diff --git a/docs/commands/index.md b/docs/commands/index.md index f8a18a9..d4a0e42 100644 --- a/docs/commands/index.md +++ b/docs/commands/index.md @@ -1,18 +1,24 @@ --- title: "Slash Commands — AI Coding Agent Commands & Codex Shortcuts" -description: "19 slash commands for Claude Code, Codex CLI, and Gemini CLI — sprint planning, tech debt analysis, PRDs, OKRs, and more." +description: "21 slash commands for Claude Code, Codex CLI, and Gemini CLI — sprint planning, tech debt analysis, PRDs, OKRs, and more." --- <div class="domain-header" markdown> # :material-console: Slash Commands -<p class="domain-count">19 commands for quick access to common operations</p> +<p class="domain-count">21 commands for quick access to common operations</p> </div> <div class="grid cards" markdown> +- :material-console:{ .lg .middle } **[`/a11y-audit`](a11y-audit.md)** + + --- + + Scan a frontend project for WCAG 2.2 accessibility issues, show fixes, and optionally check color contrast. + - :material-console:{ .lg .middle } **[`/changelog`](changelog.md)** --- @@ -97,6 +103,12 @@ description: "19 slash commands for Claude Code, Codex CLI, and Gemini CLI — s Calculate SaaS financial health metrics from raw business numbers, benchmark against industry standards, and project ... +- :material-console:{ .lg .middle } **[`/seo-auditor`](seo-auditor.md)** + + --- + + Systematically scan, audit, and optimize documentation files for SEO. Targets README.md files and docs/ pages — fixes... + - :material-console:{ .lg .middle } **[`/sprint-health`](sprint-health.md)** --- diff --git a/docs/commands/seo-auditor.md b/docs/commands/seo-auditor.md new file mode 100644 index 0000000..9f3dc84 --- /dev/null +++ b/docs/commands/seo-auditor.md @@ -0,0 +1,343 @@ +--- +title: "/seo-auditor — Slash Command for AI Coding Agents" +description: "|. Slash command for Claude Code, Codex CLI, Gemini CLI." +--- + +# /seo-auditor + +<div class="page-meta" markdown> +<span class="meta-badge">:material-console: Slash Command</span> +<span class="meta-badge">:material-github: <a href="https://github.com/alirezarezvani/claude-skills/tree/main/commands/seo-auditor.md">Source</a></span> +</div> + + +Systematically scan, audit, and optimize documentation files for SEO. Targets README.md files and docs/ pages — fixes issues in place, preserves rankings on high-performing pages, and generates a final report. + +## Usage + +```bash +/seo-auditor # Audit all docs/ and root README.md +/seo-auditor docs/skills/ # Audit a specific docs subdirectory +/seo-auditor --report-only # Scan without making changes +``` + +## What It Does + +Execute all 7 phases sequentially. Auto-fix non-destructive issues. Preserve existing high-ranking content. Report everything at the end. + +--- + +## Phase 1: Discovery & Baseline + +### 1a. Identify target files + +Scan for documentation files that need SEO audit: + +```bash +# Find all markdown files in docs/ and root README files +find docs/ -name '*.md' -type f | sort +find . -maxdepth 2 -name 'README.md' -not -path './.codex/*' -not -path './.gemini/*' | sort +``` + +Classify each file: +- **New/recently modified** — files changed in the last 2 commits (check via `git log`) +- **Index pages** — `index.md` files (high authority, handle with care) +- **Skill pages** — `docs/skills/**/*.md` (generated by `generate-docs.py`) +- **Static pages** — `docs/index.md`, `docs/getting-started.md`, `docs/integrations.md`, etc. +- **README files** — root and domain-level README.md + +### 1b. Capture baseline + +For each target file, extract current SEO state: +- `title:` frontmatter field → becomes `<title>` tag +- `description:` frontmatter field → becomes `<meta name="description">` +- First `# H1` heading +- All `## H2` and `### H3` subheadings +- Word count +- Internal link count +- External link count + +Store baseline in memory for the report. + +--- + +## Phase 2: Meta Tag Audit + +For every file with YAML frontmatter, check and fix: + +### Title Tag (`title:`) + +**Rules:** +- Must exist and be non-empty +- Length: 50-60 characters ideal (Google truncates at ~60) +- Must contain a primary keyword +- Must NOT duplicate another page's title +- For skill pages: should follow the pattern `{Skill Name} — {Differentiator} - {site_name}` +- site_name from `mkdocs.yml` is appended automatically — don't duplicate it in the title + +**Auto-fix:** If title is generic (e.g., just the skill name), enrich it with domain context using the DOMAIN_SEO_SUFFIX pattern from `scripts/generate-docs.py`. + +### Meta Description (`description:`) + +**Rules:** +- Must exist and be non-empty +- Length: 120-160 characters (Google truncates at ~160) +- Must contain the primary keyword naturally +- Must be unique across all pages — no two pages share the same description +- Should include a call-to-action or value proposition +- Must NOT start with "This page..." or "This document..." + +**Auto-fix:** If description is missing or generic, generate one from the SKILL.md frontmatter description (if available) or from the first paragraph of content. Use the `extract_description_from_frontmatter()` function from `generate-docs.py` as reference. + +### Validation Script + +Run on each file that has HTML output in `site/`: + +```bash +python3 marketing-skill/seo-audit/scripts/seo_checker.py --file site/{path}/index.html +``` + +Parse the score. Flag any page scoring below 60. + +--- + +## Phase 3: Content Quality & Readability + +For each target file, analyze and improve: + +### Heading Structure + +**Rules:** +- Exactly one `# H1` per page +- H2s follow H1, H3s follow H2 — no skipping levels +- Headings should contain keywords naturally (not stuffed) +- No duplicate headings on the same page + +**Auto-fix:** If heading levels skip (H1 → H3), adjust to proper hierarchy. + +### Readability + +Run the content scorer on each file: + +```bash +python3 marketing-skill/content-production/scripts/content_scorer.py {file_path} +``` + +Check scores for: +- **Readability** — aim for score ≥ 70 +- **Structure** — aim for score ≥ 60 +- **Engagement** — aim for score ≥ 50 + +### Content Quality Rules + +- **Paragraphs:** No single paragraph longer than 5 sentences +- **Sentences:** Average sentence length 15-20 words +- **Passive voice:** Less than 15% of sentences +- **Transition words:** At least 30% of sentences use transitions +- **Bullet lists:** Use lists for 3+ items instead of comma-separated inline lists + +### AI Content Detection + +Run the humanizer scorer on non-generated content (README.md files, static pages): + +```bash +python3 marketing-skill/content-humanizer/scripts/humanizer_scorer.py {file_path} +``` + +Flag pages scoring below 50 (too AI-sounding). For these pages, apply voice techniques from `marketing-skill/content-humanizer/references/voice-techniques.md`: +- Replace AI clichés ("delve into", "leverage", "it's important to note") +- Vary sentence length +- Add specific examples instead of generic statements +- Use active voice + +**Important:** Only modify content that was recently created or updated. Do NOT rewrite pages that are ranking well — preserve their content. + +--- + +## Phase 4: Keyword Optimization + +### 4a. Identify target keywords per page + +Based on the page's purpose and domain: + +| Page Type | Primary Keywords | Secondary Keywords | +|-----------|-----------------|-------------------| +| Homepage (docs/index.md) | "Claude Code Skills", "agent plugins" | "Codex skills", "Gemini CLI", "OpenClaw" | +| Skill pages | Skill name + "Claude Code" | "agent skill", "Codex plugin", domain terms | +| Agent pages | Agent name + "AI coding agent" | "Claude Code", "orchestrator" | +| Command pages | Command name + "slash command" | "Claude Code", "AI coding" | +| Getting started | "install Claude Code skills" | platform names | +| Domain index | Domain + "skills" + "plugins" | "Claude Code", platform names | + +### 4b. Keyword placement checks + +For each page, verify the primary keyword appears in: +- [ ] Title tag (frontmatter `title:`) +- [ ] Meta description (frontmatter `description:`) +- [ ] H1 heading +- [ ] First paragraph (within first 100 words) +- [ ] At least one H2 subheading +- [ ] Image alt text (if images present) +- [ ] URL slug (for new pages only — never change existing URLs) + +### 4c. Keyword density + +- Primary keyword: 1-2% of total word count +- Secondary keywords: 0.5-1% each +- No keyword stuffing — if density exceeds 3%, reduce it + +**Important:** Never change URLs of existing pages. URL changes break incoming links and destroy rankings. Only optimize content and meta tags. + +--- + +## Phase 5: Link Audit + +### 5a. Internal links + +For each target file, check all markdown links `[text](url)`: + +- Verify the target exists (file path resolves) +- Check for broken relative links (`../`, `./`) +- Verify anchor links (`#section-name`) point to existing headings + +**Auto-fix:** Use the `rewrite_skill_internal_links()` and `rewrite_relative_links()` functions from `generate-docs.py` as reference. Rewrite broken skill-internal links to GitHub source URLs. + +### 5b. Duplicate content detection + +Compare meta descriptions across all pages: + +```bash +grep -rh '^description:' docs/**/*.md | sort | uniq -d +``` + +If duplicates found, make each description unique by adding page-specific context. + +Compare H1 headings across all pages — no two pages should have the same H1. + +### 5c. Orphan page detection + +Check if every page in `docs/` is referenced in `mkdocs.yml` nav. Pages not in nav are orphans — they won't appear in navigation and may not be indexed. + +```bash +# Find doc pages not in mkdocs nav +find docs -name '*.md' -not -name 'index.md' | while read f; do + slug=$(echo "$f" | sed 's|docs/||') + grep -q "$slug" mkdocs.yml || echo "ORPHAN: $f" +done +``` + +**Auto-fix:** Add orphan pages to the correct nav section in `mkdocs.yml`. + +--- + +## Phase 6: Sitemap & Build + +### 6a. Rebuild the site + +```bash +mkdocs build +``` + +This regenerates `site/sitemap.xml` automatically (MkDocs Material generates it during build). + +### 6b. Verify sitemap + +Check the generated sitemap: + +```bash +python3 marketing-skill/site-architecture/scripts/sitemap_analyzer.py site/sitemap.xml +``` + +Verify: +- All documentation pages appear in the sitemap +- No broken/404 URLs +- URL count matches expected page count +- Depth distribution is reasonable (no pages deeper than 4 levels) + +### 6c. Check for sitemap issues + +- **Missing pages:** Pages in `mkdocs.yml` nav that don't appear in sitemap +- **Extra pages:** Pages in sitemap that aren't in nav (orphans) +- **Duplicate URLs:** Same page accessible via multiple URLs + +--- + +## Phase 7: Report + +Generate a concise report for the user: + +``` +╔══════════════════════════════════════════════════════════════╗ +║ SEO AUDITOR REPORT ║ +╠══════════════════════════════════════════════════════════════╣ +║ ║ +║ Pages scanned: {n} ║ +║ Issues found: {n} ║ +║ Auto-fixed: {n} ║ +║ Manual review needed: {n} ║ +║ ║ +║ META TAGS ║ +║ Titles optimized: {n} ║ +║ Descriptions fixed: {n} ║ +║ Duplicate titles: {n} → {n} (fixed) ║ +║ Duplicate descs: {n} → {n} (fixed) ║ +║ ║ +║ CONTENT ║ +║ Readability improved: {n} pages ║ +║ Heading fixes: {n} ║ +║ AI score improved: {n} pages ║ +║ ║ +║ KEYWORDS ║ +║ Pages missing primary keyword in title: {n} ║ +║ Pages missing keyword in description: {n} ║ +║ Pages with keyword stuffing: {n} ║ +║ ║ +║ LINKS ║ +║ Broken links found: {n} → {n} (fixed) ║ +║ Orphan pages: {n} → {n} (added to nav) ║ +║ Duplicate content: {n} → {n} (deduplicated) ║ +║ ║ +║ SITEMAP ║ +║ Total URLs: {n} ║ +║ Sitemap regenerated: ✅ ║ +║ ║ +║ PRESERVED (no changes — ranking well) ║ +║ {list of pages left untouched} ║ +║ ║ +╚══════════════════════════════════════════════════════════════╝ +``` + +### Pages to preserve (do NOT modify) + +These pages rank well for their target keywords. Only fix critical issues (broken links, missing meta). Do NOT rewrite content: + +- `docs/index.md` — homepage, ranks for "Claude Code Skills" +- `docs/getting-started.md` — installation guide +- `docs/integrations.md` — multi-tool support +- Any page the user explicitly marks as "preserve" + +--- + +## Skill References + +| Tool | Path | Use | +|------|------|-----| +| SEO Checker | `marketing-skill/seo-audit/scripts/seo_checker.py` | Score HTML pages 0-100 | +| Content Scorer | `marketing-skill/content-production/scripts/content_scorer.py` | Score content readability/structure/engagement | +| Humanizer Scorer | `marketing-skill/content-humanizer/scripts/humanizer_scorer.py` | Detect AI-sounding content | +| Headline Scorer | `marketing-skill/copywriting/scripts/headline_scorer.py` | Score title quality | +| SEO Optimizer | `marketing-skill/content-production/scripts/seo_optimizer.py` | Optimize content for target keyword | +| Sitemap Analyzer | `marketing-skill/site-architecture/scripts/sitemap_analyzer.py` | Analyze sitemap structure | +| Schema Validator | `marketing-skill/schema-markup/scripts/schema_validator.py` | Validate structured data | +| Topic Cluster Mapper | `marketing-skill/content-strategy/scripts/topic_cluster_mapper.py` | Group pages into content clusters | + +### Reference Docs + +| Reference | Path | Use | +|-----------|------|-----| +| SEO Audit Framework | `marketing-skill/seo-audit/references/seo-audit-reference.md` | Priority order for SEO fixes | +| AI Search Optimization | `marketing-skill/ai-seo/references/content-patterns.md` | Make content citable by AI | +| Content Optimization | `marketing-skill/content-production/references/optimization-checklist.md` | Pre-publish checklist | +| URL Design Guide | `marketing-skill/site-architecture/references/url-design-guide.md` | URL structure best practices | +| Internal Linking | `marketing-skill/site-architecture/references/internal-linking-playbook.md` | Internal linking strategy | +| AI Writing Detection | `marketing-skill/content-humanizer/references/ai-tells-checklist.md` | AI cliché removal | diff --git a/docs/getting-started.md b/docs/getting-started.md index 9b7a84f..b48d17d 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -140,7 +140,7 @@ Choose your platform and follow the steps: | Bundle | Install Command | Skills | |--------|----------------|--------| -| **Engineering Core** | `/plugin install engineering-skills@claude-code-skills` | 25 | +| **Engineering Core** | `/plugin install engineering-skills@claude-code-skills` | 26 | | **Engineering POWERFUL** | `/plugin install engineering-advanced-skills@claude-code-skills` | 30 | | **Product** | `/plugin install product-skills@claude-code-skills` | 14 | | **Marketing** | `/plugin install marketing-skills@claude-code-skills` | 43 | diff --git a/docs/index.md b/docs/index.md index 0e5f14c..ef4de02 100644 --- a/docs/index.md +++ b/docs/index.md @@ -89,7 +89,7 @@ hide: [:octicons-arrow-right-24: Getting started](getting-started.md) -- :material-puzzle-outline:{ .lg .middle } **22 Plugins** +- :material-puzzle-outline:{ .lg .middle } **28 Plugins** --- @@ -135,7 +135,7 @@ hide: Architecture, frontend, backend, fullstack, QA, DevOps, SecOps, AI/ML, data engineering, Playwright testing, self-improving agent - [:octicons-arrow-right-24: 25 skills](skills/engineering-team/) + [:octicons-arrow-right-24: 26 skills](skills/engineering-team/) - :material-lightning-bolt:{ .lg .middle } **Engineering — Advanced** diff --git a/docs/plugins/index.md b/docs/plugins/index.md index f0b086b..b991194 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -1,13 +1,13 @@ --- title: "Agent Plugin Marketplace — Codex & OpenClaw Plugins" -description: "21 installable agent plugins for Claude Code, Codex CLI, Gemini CLI, and OpenClaw. One-command install for engineering, marketing, product, compliance, and finance skill bundles." +description: "28 installable agent plugins for Claude Code, Codex CLI, Gemini CLI, and OpenClaw. One-command install for engineering, marketing, product, compliance, and finance skill bundles." --- <div class="skills-hero" markdown> # Plugins & Marketplace -**19 installable plugins** — domain bundles and standalone packages distributed via Claude Code plugin registry and ClawHub. +**28 installable plugins** — domain bundles and standalone packages distributed via Claude Code plugin registry and ClawHub. <p class="skills-hero-sub">Install entire skill domains or individual tools with a single command. Compatible with Claude Code, OpenAI Codex, Gemini CLI, and OpenClaw.</p> @@ -19,7 +19,7 @@ description: "21 installable agent plugins for Claude Code, Codex CLI, Gemini CL <div class="grid cards" markdown> -- :material-puzzle-outline:{ .lg .middle } **19 Plugins** +- :material-puzzle-outline:{ .lg .middle } **28 Plugins** --- @@ -90,7 +90,7 @@ description: "21 installable agent plugins for Claude Code, Codex CLI, Gemini CL ```mermaid graph TB subgraph Registry["Plugin Registry"] - MP["marketplace.json<br/>19 plugins"] + MP["marketplace.json<br/>28 plugins"] end subgraph Bundles["Domain Bundles (9)"] diff --git a/docs/skills/engineering-team/a11y-audit.md b/docs/skills/engineering-team/a11y-audit.md new file mode 100644 index 0000000..702a741 --- /dev/null +++ b/docs/skills/engineering-team/a11y-audit.md @@ -0,0 +1,1373 @@ +--- +title: "Accessibility Audit — Agent Skill & Codex Plugin" +description: "Accessibility audit skill for scanning, fixing, and verifying WCAG 2.2 Level A and AA compliance across React, Next.js, Vue, Angular, Svelte, and. Agent skill for Claude Code, Codex CLI, Gemini CLI, OpenClaw." +--- + +# Accessibility Audit + +<div class="page-meta" markdown> +<span class="meta-badge">:material-code-braces: Engineering - Core</span> +<span class="meta-badge">:material-identifier: `a11y-audit`</span> +<span class="meta-badge">:material-github: <a href="https://github.com/alirezarezvani/claude-skills/tree/main/engineering-team/a11y-audit/SKILL.md">Source</a></span> +</div> + +<div class="install-banner" markdown> +<span class="install-label">Install:</span> <code>claude /plugin install engineering-skills</code> +</div> + +**Name**: a11y-audit +**Tier**: STANDARD +**Category**: Engineering - Frontend Quality +**Dependencies**: Python 3.8+ (Standard Library Only) +**Author**: Alireza Rezvani +**Version**: 2.1.2 +**Last Updated**: 2026-03-18 +**License**: MIT + +--- + +## Name + +a11y-audit -- WCAG 2.2 Accessibility Audit and Remediation Skill + +## Description + +The 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. + +This skill goes beyond detection. For every violation it finds, it provides the precise before/after code fix tailored to your framework (React, Next.js, Vue, Angular, Svelte, or plain HTML). It understands that a missing `alt` attribute on an `<img>` in React JSX requires a different fix pattern than the same issue in a Vue SFC or an Angular template. + +**What this skill does:** + +1. **Scans** your codebase for every WCAG 2.2 Level A and AA violation, categorized by severity (Critical, Major, Minor) +2. **Fixes** each violation with framework-specific before/after code patterns +3. **Verifies** that fixes resolve the original violations and introduces no regressions +4. **Reports** findings in a structured format suitable for developers, PMs, and compliance stakeholders +5. **Integrates** into CI/CD pipelines to prevent accessibility regressions + +**Key differentiators:** + +- Framework-aware fix patterns (not generic HTML advice) +- Color contrast analysis with accessible alternative suggestions +- WCAG 2.2 coverage including the newest success criteria (Focus Appearance, Dragging Movements, Target Size) +- CI/CD pipeline integration with GitHub Actions, GitLab CI, and Azure DevOps +- Slash command support via `/a11y-audit` + +## Features + +### Core Capabilities + +| Feature | Description | +|---------|-------------| +| **Full WCAG 2.2 Scan** | Checks all Level A and AA success criteria across your codebase | +| **Framework Detection** | Auto-detects React, Next.js, Vue, Angular, Svelte, or plain HTML | +| **Severity Classification** | Categorizes each violation as Critical, Major, or Minor | +| **Fix Code Generation** | Produces before/after code diffs for every issue | +| **Color Contrast Checker** | Validates foreground/background pairs against AA and AAA ratios | +| **Accessible Alternatives** | Suggests replacement colors that meet contrast requirements | +| **Compliance Reporting** | Generates stakeholder reports with pass/fail summaries | +| **CI/CD Integration** | GitHub Actions, GitLab CI, Azure DevOps pipeline configs | +| **Keyboard Navigation Audit** | Detects missing focus management and tab order issues | +| **ARIA Validation** | Checks for incorrect, redundant, or missing ARIA attributes | +| **Live Region Detection** | Identifies dynamic content lacking `aria-live` announcements | +| **Form Accessibility** | Validates label associations, error messaging, and input types | + +### WCAG 2.2 Coverage Matrix + +| Principle | Level A Criteria | Level AA Criteria | +|-----------|-----------------|-------------------| +| **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 | +| **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) | +| **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) | +| **Robust** | 4.1.2 Name/Role/Value | 4.1.3 Status Messages | + +### Severity Definitions + +| Severity | Definition | Example | SLA | +|----------|-----------|---------|-----| +| **Critical** | Blocks access for entire user groups | Missing alt text on informational images, no keyboard access to primary navigation | Fix before release | +| **Major** | Significant barrier that degrades experience | Insufficient color contrast on body text, missing form labels | Fix within current sprint | +| **Minor** | Usability issue that causes friction | Redundant ARIA roles, suboptimal heading hierarchy | Fix within next 2 sprints | + +## Usage + +### Quick Start + +Activate the skill and run an audit on your project: + +```bash +# Scan entire project +python scripts/a11y_scanner.py /path/to/project + +# Scan with JSON output for tooling +python scripts/a11y_scanner.py /path/to/project --json + +# Check color contrast for specific values +python scripts/contrast_checker.py --fg "#777777" --bg "#ffffff" + +# Check contrast across a CSS/Tailwind file +python scripts/contrast_checker.py --file /path/to/styles.css +``` + +### Slash Command + +Use the `/a11y-audit` slash command for an interactive audit session: + +``` +/a11y-audit # Audit current project +/a11y-audit --scope src/ # Audit specific directory +/a11y-audit --fix # Audit and auto-apply fixes +/a11y-audit --report # Generate stakeholder report +/a11y-audit --ci # Output CI-compatible results +``` + +### Three-Phase Workflow + +#### Phase 1: Scan + +The scanner walks your source tree, detects the framework in use, and applies the appropriate rule set. + +```bash +python scripts/a11y_scanner.py /path/to/project --format table +``` + +**Sample output:** + +``` +A11Y AUDIT REPORT - /path/to/project +Framework Detected: React (Next.js) +Files Scanned: 127 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +CRITICAL (3 issues) + [1.1.1] src/components/Hero.tsx:14 + Missing alt text on <img> element + [2.1.1] src/components/Modal.tsx:8 + Focus not trapped inside modal dialog + [1.4.3] src/styles/globals.css:42 + Contrast ratio 2.8:1 on .subtitle (requires 4.5:1) + +MAJOR (7 issues) + [2.4.11] src/components/Button.tsx:22 + Focus indicator not visible (2px outline required) + [1.3.1] src/components/Form.tsx:31 + Input missing associated <label> + ... + +MINOR (4 issues) + [1.3.1] src/components/Nav.tsx:5 + <nav> has redundant role="navigation" + ... + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +SUMMARY: 14 violations (3 Critical, 7 Major, 4 Minor) +WCAG 2.2 Level A: 8 issues +WCAG 2.2 Level AA: 6 issues +``` + +#### Phase 2: Fix + +For each violation, apply the framework-specific fix. The skill provides exact before/after code for every issue type. + +See the [Fix Patterns by Framework](#fix-patterns-by-framework) section below for the complete fix catalog. + +#### Phase 3: Verify + +Re-run the scanner to confirm all fixes are applied and no regressions were introduced: + +```bash +# Re-scan after fixes +python scripts/a11y_scanner.py /path/to/project --format table + +# Compare against baseline +python scripts/a11y_scanner.py /path/to/project --baseline audit-baseline.json +``` + +**Verification output:** + +``` +VERIFICATION RESULTS +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +Previous Scan: 14 violations (3 Critical, 7 Major, 4 Minor) +Current Scan: 2 violations (0 Critical, 1 Major, 1 Minor) +Resolved: 12 violations +New Issues: 0 regressions + +STATUS: IMPROVED (85.7% reduction) +``` + +## Examples + +### Example 1: React Component Audit + +Given a React component with multiple accessibility issues: + +```tsx +// BEFORE: src/components/ProductCard.tsx +function ProductCard({ product }) { + return ( + <div onClick={() => navigate(`/product/${product.id}`)}> + <img src={product.image} /> + <div style={{ color: '#aaa', fontSize: '12px' }}> + {product.name} + </div> + <span style={{ color: '#999' }}>${product.price}</span> + </div> + ); +} +``` + +**Violations detected:** + +| # | WCAG | Severity | Issue | +|---|------|----------|-------| +| 1 | 1.1.1 | Critical | `<img>` missing `alt` attribute | +| 2 | 2.1.1 | Critical | `<div onClick>` not keyboard accessible | +| 3 | 1.4.3 | Major | Color `#aaa` on white fails contrast (2.32:1, needs 4.5:1) | +| 4 | 1.4.3 | Major | Color `#999` on white fails contrast (2.85:1, needs 4.5:1) | +| 5 | 4.1.2 | Major | Interactive element missing role and accessible name | + +```tsx +// AFTER: src/components/ProductCard.tsx +function ProductCard({ product }) { + return ( + <a + href={`/product/${product.id}`} + className="product-card" + aria-label={`View ${product.name} - $${product.price}`} + > + <img src={product.image} alt={product.imageAlt || product.name} /> + <div style={{ color: '#595959', fontSize: '12px' }}> + {product.name} + </div> + <span style={{ color: '#767676' }}>${product.price}</span> + </a> + ); +} +``` + +**What changed:** +- `<div onClick>` replaced with `<a href>` for native keyboard and screen reader support +- `alt` attribute added to `<img>` with meaningful fallback +- `aria-label` provides full context for assistive technology +- Color `#aaa` replaced with `#595959` (7.01:1 contrast ratio -- passes AA and AAA) +- Color `#999` replaced with `#767676` (4.54:1 contrast ratio -- passes AA) + +### Example 2: Vue SFC Form Audit + +```vue +<!-- BEFORE: src/components/LoginForm.vue --> +<template> + <form @submit="handleLogin"> + <input type="text" placeholder="Email" v-model="email" /> + <input type="password" placeholder="Password" v-model="password" /> + <div v-if="error" style="color: red">{{ error }}</div> + <div @click="handleLogin">Sign In</div> + </form> +</template> +``` + +**Violations detected:** + +| # | WCAG | Severity | Issue | +|---|------|----------|-------| +| 1 | 1.3.1 | Critical | Inputs missing associated `<label>` elements | +| 2 | 3.3.2 | Major | Placeholder text used as only label (disappears on input) | +| 3 | 2.1.1 | Critical | `<div @click>` not keyboard accessible | +| 4 | 4.1.3 | Major | Error message not announced to screen readers | +| 5 | 3.3.1 | Major | Error not programmatically associated with input | + +```vue +<!-- AFTER: src/components/LoginForm.vue --> +<template> + <form @submit.prevent="handleLogin" aria-label="Sign in to your account"> + <div class="field"> + <label for="login-email">Email</label> + <input + id="login-email" + type="email" + v-model="email" + autocomplete="email" + required + :aria-describedby="emailError ? 'email-error' : undefined" + :aria-invalid="!!emailError" + /> + <span v-if="emailError" id="email-error" role="alert"> + {{ emailError }} + </span> + </div> + <div class="field"> + <label for="login-password">Password</label> + <input + id="login-password" + type="password" + v-model="password" + autocomplete="current-password" + required + :aria-describedby="passwordError ? 'password-error' : undefined" + :aria-invalid="!!passwordError" + /> + <span v-if="passwordError" id="password-error" role="alert"> + {{ passwordError }} + </span> + </div> + <div v-if="error" role="alert" aria-live="assertive" class="form-error"> + {{ error }} + </div> + <button type="submit">Sign In</button> + </form> +</template> +``` + +### Example 3: Angular Template Audit + +```html +<!-- BEFORE: src/app/dashboard/dashboard.component.html --> +<div class="tabs"> + <div *ngFor="let tab of tabs" + (click)="selectTab(tab)" + [class.active]="tab.active"> + {{ tab.label }} + </div> +</div> +<div class="tab-content"> + <div *ngIf="selectedTab">{{ selectedTab.content }}</div> +</div> +``` + +**Violations detected:** + +| # | WCAG | Severity | Issue | +|---|------|----------|-------| +| 1 | 4.1.2 | Critical | Tab widget missing ARIA roles (`tablist`, `tab`, `tabpanel`) | +| 2 | 2.1.1 | Critical | Tabs not keyboard navigable (arrow keys, Home, End) | +| 3 | 2.4.11 | Major | No visible focus indicator on active tab | + +```html +<!-- AFTER: src/app/dashboard/dashboard.component.html --> +<div class="tabs" role="tablist" aria-label="Dashboard sections"> + <button + *ngFor="let tab of tabs; let i = index" + role="tab" + [id]="'tab-' + tab.id" + [attr.aria-selected]="tab.active" + [attr.aria-controls]="'panel-' + tab.id" + [attr.tabindex]="tab.active ? 0 : -1" + (click)="selectTab(tab)" + (keydown)="handleTabKeydown($event, i)" + class="tab-button" + [class.active]="tab.active"> + {{ tab.label }} + </button> +</div> +<div + *ngIf="selectedTab" + role="tabpanel" + [id]="'panel-' + selectedTab.id" + [attr.aria-labelledby]="'tab-' + selectedTab.id" + tabindex="0" + class="tab-content"> + {{ selectedTab.content }} +</div> +``` + +**Supporting TypeScript for keyboard navigation:** + +```typescript +// dashboard.component.ts +handleTabKeydown(event: KeyboardEvent, index: number): void { + const tabCount = this.tabs.length; + let newIndex = index; + + switch (event.key) { + case 'ArrowRight': + newIndex = (index + 1) % tabCount; + break; + case 'ArrowLeft': + newIndex = (index - 1 + tabCount) % tabCount; + break; + case 'Home': + newIndex = 0; + break; + case 'End': + newIndex = tabCount - 1; + break; + default: + return; + } + + event.preventDefault(); + this.selectTab(this.tabs[newIndex]); + // Move focus to the new tab button + const tabElement = document.getElementById(`tab-${this.tabs[newIndex].id}`); + tabElement?.focus(); +} +``` + +### Example 4: Next.js Page-Level Audit + +```tsx +// BEFORE: src/app/page.tsx +export default function Home() { + return ( + <main> + <div className="text-4xl font-bold">Welcome to Acme</div> + <div className="mt-4"> + Build better products with our platform. + </div> + <div className="mt-8 bg-blue-600 text-white px-6 py-3 rounded cursor-pointer" + onClick={() => router.push('/signup')}> + Get Started + </div> + </main> + ); +} +``` + +**Violations detected:** + +| # | WCAG | Severity | Issue | +|---|------|----------|-------| +| 1 | 1.3.1 | Major | Heading uses `<div>` instead of `<h1>` -- no semantic structure | +| 2 | 2.4.2 | Major | Page missing `<title>` (Next.js metadata) | +| 3 | 2.1.1 | Critical | CTA uses `<div onClick>` -- not keyboard accessible | +| 4 | 3.1.1 | Minor | `<html>` missing `lang` attribute (check `layout.tsx`) | + +```tsx +// AFTER: src/app/page.tsx +import type { Metadata } from 'next'; +import Link from 'next/link'; + +export const metadata: Metadata = { + title: 'Acme - Build Better Products', + description: 'Build better products with the Acme platform.', +}; + +export default function Home() { + return ( + <main> + <h1 className="text-4xl font-bold">Welcome to Acme</h1> + <p className="mt-4"> + Build better products with our platform. + </p> + <Link + href="/signup" + className="mt-8 inline-block bg-blue-600 text-white px-6 py-3 rounded + hover:bg-blue-700 focus-visible:outline focus-visible:outline-2 + focus-visible:outline-offset-2 focus-visible:outline-blue-600" + > + Get Started + </Link> + </main> + ); +} +``` + +```tsx +// Also fix: src/app/layout.tsx +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + <html lang="en"> + <body>{children}</body> + </html> + ); +} +``` + +### Example 5: Svelte Component Audit + +```svelte +<!-- BEFORE: src/lib/components/Accordion.svelte --> +<script> + let openIndex = -1; + function toggle(i) { openIndex = openIndex === i ? -1 : i; } +</script> + +{#each items as item, i} + <div class="header" on:click={() => toggle(i)}> + {item.title} + </div> + {#if openIndex === i} + <div class="content">{item.body}</div> + {/if} +{/each} +``` + +**Violations detected:** + +| # | WCAG | Severity | Issue | +|---|------|----------|-------| +| 1 | 4.1.2 | Critical | Accordion missing ARIA roles and properties | +| 2 | 2.1.1 | Critical | Headers not keyboard accessible | +| 3 | 2.5.8 | Minor | Click targets may be smaller than 24x24px (NEW in WCAG 2.2) | + +```svelte +<!-- AFTER: src/lib/components/Accordion.svelte --> +<script> + export let items = []; + let openIndex = -1; + + function toggle(i) { + openIndex = openIndex === i ? -1 : i; + } + + function handleKeydown(event, i) { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + toggle(i); + } + } +</script> + +<div class="accordion"> + {#each items as item, i} + <h3> + <button + class="accordion-header" + aria-expanded={openIndex === i} + aria-controls="panel-{i}" + id="header-{i}" + on:click={() => toggle(i)} + on:keydown={(e) => handleKeydown(e, i)} + > + {item.title} + <span class="icon" aria-hidden="true"> + {openIndex === i ? '−' : '+'} + </span> + </button> + </h3> + <div + id="panel-{i}" + role="region" + aria-labelledby="header-{i}" + class="accordion-content" + class:open={openIndex === i} + hidden={openIndex !== i} + > + {item.body} + </div> + {/each} +</div> + +<style> + .accordion-header { + min-height: 44px; /* WCAG 2.5.8 Target Size */ + width: 100%; + padding: 12px 16px; + cursor: pointer; + text-align: left; + } + .accordion-header:focus-visible { + outline: 2px solid #005fcc; + outline-offset: 2px; + } +</style> +``` + +## Fix Patterns by Framework + +### React / Next.js Fix Patterns + +#### Missing Alt Text (1.1.1) + +```tsx +// BEFORE +<img src={hero} /> + +// AFTER - Informational image +<img src={hero} alt="Team collaborating around a whiteboard" /> + +// AFTER - Decorative image +<img src={divider} alt="" role="presentation" /> +``` + +#### Non-Interactive Element with Click Handler (2.1.1) + +```tsx +// BEFORE +<div onClick={handleClick}>Click me</div> + +// AFTER - If it navigates +<Link href="/destination">Click me</Link> + +// AFTER - If it performs an action +<button type="button" onClick={handleClick}>Click me</button> +``` + +#### Missing Focus Management in Modals (2.4.3) + +```tsx +// BEFORE +function Modal({ isOpen, onClose, children }) { + if (!isOpen) return null; + return <div className="modal-overlay">{children}</div>; +} + +// AFTER +import { useEffect, useRef } from 'react'; + +function Modal({ isOpen, onClose, children, title }) { + const modalRef = useRef(null); + const previousFocus = useRef(null); + + useEffect(() => { + if (isOpen) { + previousFocus.current = document.activeElement; + modalRef.current?.focus(); + } else { + previousFocus.current?.focus(); + } + }, [isOpen]); + + useEffect(() => { + if (!isOpen) return; + const handleKeydown = (e) => { + if (e.key === 'Escape') onClose(); + if (e.key === 'Tab') { + const focusable = modalRef.current?.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ); + if (!focusable?.length) return; + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); + last.focus(); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); + first.focus(); + } + } + }; + document.addEventListener('keydown', handleKeydown); + return () => document.removeEventListener('keydown', handleKeydown); + }, [isOpen, onClose]); + + if (!isOpen) return null; + + return ( + <div className="modal-overlay" onClick={onClose} aria-hidden="true"> + <div + ref={modalRef} + role="dialog" + aria-modal="true" + aria-label={title} + tabIndex={-1} + onClick={(e) => e.stopPropagation()} + > + <button + onClick={onClose} + aria-label="Close dialog" + className="modal-close" + > + × + </button> + {children} + </div> + </div> + ); +} +``` + +#### Focus Appearance (2.4.11 -- NEW in WCAG 2.2) + +```css +/* BEFORE */ +button:focus { + outline: none; /* Removes default focus indicator */ +} + +/* AFTER - Meets WCAG 2.2 Focus Appearance */ +button:focus-visible { + outline: 2px solid #005fcc; + outline-offset: 2px; +} +``` + +```tsx +// Tailwind CSS pattern +<button className="focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"> + Submit +</button> +``` + +### Vue Fix Patterns + +#### Missing Form Labels (1.3.1) + +```vue +<!-- BEFORE --> +<input type="text" v-model="name" placeholder="Name" /> + +<!-- AFTER --> +<label for="user-name">Name</label> +<input id="user-name" type="text" v-model="name" autocomplete="name" /> +``` + +#### Dynamic Content Without Live Region (4.1.3) + +```vue +<!-- BEFORE --> +<div v-if="status">{{ statusMessage }}</div> + +<!-- AFTER --> +<div aria-live="polite" aria-atomic="true"> + <p v-if="status">{{ statusMessage }}</p> +</div> +``` + +#### Vue Router Navigation Announcements (2.4.2) + +```typescript +// router/index.ts +router.afterEach((to) => { + const title = to.meta.title || 'Page'; + document.title = `${title} | My App`; + + // Announce route change to screen readers + const announcer = document.getElementById('route-announcer'); + if (announcer) { + announcer.textContent = `Navigated to ${title}`; + } +}); +``` + +```vue +<!-- App.vue - Add announcer element --> +<div + id="route-announcer" + role="status" + aria-live="assertive" + aria-atomic="true" + class="sr-only" +></div> +``` + +### Angular Fix Patterns + +#### Missing ARIA on Custom Components (4.1.2) + +```typescript +// BEFORE +@Component({ + selector: 'app-dropdown', + template: ` + <div (click)="toggle()">{{ selected }}</div> + <div *ngIf="isOpen"> + <div *ngFor="let opt of options" (click)="select(opt)">{{ opt }}</div> + </div> + ` +}) + +// AFTER +@Component({ + selector: 'app-dropdown', + template: ` + <button + role="combobox" + [attr.aria-expanded]="isOpen" + aria-haspopup="listbox" + [attr.aria-label]="label" + (click)="toggle()" + (keydown)="handleKeydown($event)" + > + {{ selected }} + </button> + <ul *ngIf="isOpen" role="listbox" [attr.aria-label]="label + ' options'"> + <li + *ngFor="let opt of options; let i = index" + role="option" + [attr.aria-selected]="opt === selected" + [attr.id]="'option-' + i" + (click)="select(opt)" + (keydown)="handleOptionKeydown($event, opt, i)" + tabindex="-1" + > + {{ opt }} + </li> + </ul> + ` +}) +``` + +#### Angular CDK A11y Module Integration + +```typescript +// Use Angular CDK for focus trap in dialogs +import { A11yModule } from '@angular/cdk/a11y'; + +@Component({ + template: ` + <div cdkTrapFocus cdkTrapFocusAutoCapture> + <h2 id="dialog-title">Edit Profile</h2> + <!-- dialog content --> + </div> + ` +}) +``` + +### Svelte Fix Patterns + +#### Accessible Announcements (4.1.3) + +```svelte +<!-- BEFORE --> +{#if message} + <p class="toast">{message}</p> +{/if} + +<!-- AFTER --> +<div aria-live="polite" class="sr-only"> + {#if message} + <p>{message}</p> + {/if} +</div> +<div class="toast" aria-hidden="true"> + {#if message} + <p>{message}</p> + {/if} +</div> +``` + +#### SvelteKit Page Titles (2.4.2) + +```svelte +<!-- +page.svelte --> +<svelte:head> + <title>Dashboard | My App + +``` + +### Plain HTML Fix Patterns + +#### Skip Navigation Link (2.4.1) + +```html + + + +
+ + + + + + +
+ +``` + +```css +.skip-link { + position: absolute; + top: -40px; + left: 0; + padding: 8px 16px; + background: #005fcc; + color: #fff; + z-index: 1000; + transition: top 0.2s; +} +.skip-link:focus { + top: 0; +} +``` + +#### Accessible Data Table (1.3.1) + +```html + + + + +
NameEmailRole
Alicealice@co.comAdmin
+ + + + + + + + + + + + + + + + + + +
List of team members and their roles
NameEmailRole
Alicealice@co.comAdmin
+``` + +## Color Contrast Checker + +The `contrast_checker.py` script validates color pairs against WCAG 2.2 contrast requirements. + +### Usage + +```bash +# Check a single color pair +python scripts/contrast_checker.py --fg "#777777" --bg "#ffffff" + +# Output: +# Foreground: #777777 | Background: #ffffff +# Contrast Ratio: 4.48:1 +# AA Normal Text (4.5:1): FAIL +# AA Large Text (3.0:1): PASS +# AAA Normal Text (7.0:1): FAIL +# Suggested alternative: #767676 (4.54:1 - passes AA) + +# Scan a CSS file for all color pairs +python scripts/contrast_checker.py --file src/styles/globals.css + +# Scan Tailwind classes in components +python scripts/contrast_checker.py --tailwind src/components/ +``` + +### Common Contrast Fixes + +| Original Color | Contrast on White | Fix | New Contrast | +|----------------|------------------|-----|--------------| +| `#aaaaaa` | 2.32:1 | `#767676` | 4.54:1 (AA) | +| `#999999` | 2.85:1 | `#767676` | 4.54:1 (AA) | +| `#888888` | 3.54:1 | `#767676` | 4.54:1 (AA) | +| `#777777` | 4.48:1 | `#757575` | 4.60:1 (AA) | +| `#66bb6a` | 3.06:1 | `#2e7d32` | 5.87:1 (AA) | +| `#42a5f5` | 2.81:1 | `#1565c0` | 6.08:1 (AA) | +| `#ef5350` | 3.13:1 | `#c62828` | 5.57:1 (AA) | + +### Tailwind CSS Accessible Palette Mapping + +| Inaccessible Class | Contrast on White | Accessible Alternative | Contrast | +|---------------------|------------------|----------------------|----------| +| `text-gray-400` | 2.68:1 | `text-gray-600` | 5.74:1 | +| `text-blue-400` | 2.81:1 | `text-blue-700` | 5.96:1 | +| `text-green-400` | 2.12:1 | `text-green-700` | 5.18:1 | +| `text-red-400` | 3.04:1 | `text-red-700` | 6.05:1 | +| `text-yellow-500` | 1.47:1 | `text-yellow-800` | 7.34:1 | + +## CI/CD Integration + +### GitHub Actions + +```yaml +# .github/workflows/a11y-audit.yml +name: Accessibility Audit + +on: + pull_request: + paths: + - 'src/**/*.tsx' + - 'src/**/*.vue' + - 'src/**/*.html' + - 'src/**/*.svelte' + +jobs: + a11y-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Run A11y Scanner + run: | + python scripts/a11y_scanner.py ./src --json > a11y-results.json + + - name: Check for Critical Issues + run: | + python -c " + import json, sys + with open('a11y-results.json') as f: + data = json.load(f) + critical = [v for v in data.get('violations', []) if v['severity'] == 'critical'] + if critical: + print(f'FAILED: {len(critical)} critical a11y violations found') + for v in critical: + print(f\" [{v['wcag']}] {v['file']}:{v['line']} - {v['message']}\") + sys.exit(1) + print('PASSED: No critical a11y violations') + " + + - name: Upload Audit Report + if: always() + uses: actions/upload-artifact@v4 + with: + name: a11y-audit-report + path: a11y-results.json + + - name: Comment on PR + if: failure() + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: a11y-audit + message: | + ## Accessibility Audit Failed + Critical WCAG 2.2 violations were found. See the uploaded artifact for details. + Run `python scripts/a11y_scanner.py ./src` locally to view and fix issues. +``` + +### GitLab CI + +```yaml +# .gitlab-ci.yml +a11y-audit: + stage: test + image: python:3.11-slim + script: + - python scripts/a11y_scanner.py ./src --json > a11y-results.json + - python -c " + import json, sys; + data = json.load(open('a11y-results.json')); + critical = [v for v in data.get('violations', []) if v['severity'] == 'critical']; + sys.exit(1) if critical else print('A11y audit passed') + " + artifacts: + paths: + - a11y-results.json + when: always + rules: + - changes: + - "src/**/*.{tsx,vue,html,svelte}" +``` + +### Azure DevOps + +```yaml +# azure-pipelines.yml +- task: PythonScript@0 + displayName: 'Run A11y Audit' + inputs: + scriptSource: 'filePath' + scriptPath: 'scripts/a11y_scanner.py' + arguments: './src --json --output $(Build.ArtifactStagingDirectory)/a11y-results.json' + +- task: PublishBuildArtifacts@1 + condition: always() + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/a11y-results.json' + ArtifactName: 'a11y-audit-report' +``` + +### Pre-Commit Hook + +```bash +#!/bin/bash +# .git/hooks/pre-commit + +# Run a11y scan on staged files only +STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(tsx|vue|html|svelte|jsx)$') + +if [ -n "$STAGED_FILES" ]; then + echo "Running accessibility audit on staged files..." + for file in $STAGED_FILES; do + python scripts/a11y_scanner.py "$file" --severity critical --quiet + if [ $? -ne 0 ]; then + echo "A11y audit FAILED for $file. Fix critical issues before committing." + exit 1 + fi + done + echo "A11y audit passed." +fi +``` + +## Common Pitfalls + +| Pitfall | Why It Happens | Correct Approach | +|---------|---------------|------------------| +| Using `role="button"` on a `
` | Developers think ARIA makes any element interactive | Use a native `