{
  "schemaVersion": "1.0",
  "item": {
    "slug": "accessibility",
    "name": "Accessibility",
    "source": "tencent",
    "type": "skill",
    "category": "AI 智能",
    "sourceUrl": "https://clawhub.ai/Veeramanikandanr48/accessibility",
    "canonicalUrl": "https://clawhub.ai/Veeramanikandanr48/accessibility",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/accessibility",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=accessibility",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      ".claude-plugin/plugin.json",
      "README.md",
      "SKILL.md",
      "agents/a11y-auditor.md",
      "references/aria-patterns.md",
      "references/color-contrast.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-23T16:43:11.935Z",
      "expiresAt": "2026-04-30T16:43:11.935Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=4claw-imageboard",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=4claw-imageboard",
        "contentDisposition": "attachment; filename=\"4claw-imageboard-1.0.1.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/accessibility"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/accessibility",
    "agentPageUrl": "https://openagent3.xyz/skills/accessibility/agent",
    "manifestUrl": "https://openagent3.xyz/skills/accessibility/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/accessibility/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Web Accessibility (WCAG 2.1 AA)",
        "body": "Status: Production Ready ✅\nLast Updated: 2026-01-14\nDependencies: None (framework-agnostic)\nStandards: WCAG 2.1 Level AA"
      },
      {
        "title": "1. Semantic HTML Foundation",
        "body": "Choose the right element - don't use div for everything:\n\n<!-- ❌ WRONG - divs with onClick -->\n<div onclick=\"submit()\">Submit</div>\n<div onclick=\"navigate()\">Next page</div>\n\n<!-- ✅ CORRECT - semantic elements -->\n<button type=\"submit\">Submit</button>\n<a href=\"/next\">Next page</a>\n\nWhy this matters:\n\nSemantic elements have built-in keyboard support\nScreen readers announce role automatically\nBrowser provides default accessible behaviors"
      },
      {
        "title": "2. Focus Management",
        "body": "Make interactive elements keyboard-accessible:\n\n/* ❌ WRONG - removes focus outline */\nbutton:focus { outline: none; }\n\n/* ✅ CORRECT - custom accessible outline */\nbutton:focus-visible {\n  outline: 2px solid var(--primary);\n  outline-offset: 2px;\n}\n\nCRITICAL:\n\nNever remove focus outlines without replacement\nUse :focus-visible to show only on keyboard focus\nEnsure 3:1 contrast ratio for focus indicators"
      },
      {
        "title": "3. Text Alternatives",
        "body": "Every non-text element needs a text alternative:\n\n<!-- ❌ WRONG - no alt text -->\n<img src=\"logo.png\">\n<button><svg>...</svg></button>\n\n<!-- ✅ CORRECT - proper alternatives -->\n<img src=\"logo.png\" alt=\"Company Name\">\n<button aria-label=\"Close dialog\"><svg>...</svg></button>"
      },
      {
        "title": "Step 1: Choose Semantic HTML",
        "body": "Decision tree for element selection:\n\nNeed clickable element?\n├─ Navigates to another page? → <a href=\"...\">\n├─ Submits form? → <button type=\"submit\">\n├─ Opens dialog? → <button aria-haspopup=\"dialog\">\n└─ Other action? → <button type=\"button\">\n\nGrouping content?\n├─ Self-contained article? → <article>\n├─ Thematic section? → <section>\n├─ Navigation links? → <nav>\n└─ Supplementary info? → <aside>\n\nForm element?\n├─ Text input? → <input type=\"text\">\n├─ Multiple choice? → <select> or <input type=\"radio\">\n├─ Toggle? → <input type=\"checkbox\"> or <button aria-pressed>\n└─ Long text? → <textarea>\n\nSee references/semantic-html.md for complete guide."
      },
      {
        "title": "Step 2: Add ARIA When Needed",
        "body": "Golden rule: Use ARIA only when HTML can't express the pattern.\n\n<!-- ❌ WRONG - unnecessary ARIA -->\n<button role=\"button\">Click me</button>  <!-- Button already has role -->\n\n<!-- ✅ CORRECT - ARIA fills semantic gap -->\n<div role=\"dialog\" aria-labelledby=\"title\" aria-modal=\"true\">\n  <h2 id=\"title\">Confirm action</h2>\n  <!-- No HTML dialog yet, so role needed -->\n</div>\n\n<!-- ✅ BETTER - Use native HTML when available -->\n<dialog aria-labelledby=\"title\">\n  <h2 id=\"title\">Confirm action</h2>\n</dialog>\n\nCommon ARIA patterns:\n\naria-label - When visible label doesn't exist\naria-labelledby - Reference existing text as label\naria-describedby - Additional description\naria-live - Announce dynamic updates\naria-expanded - Collapsible/expandable state\n\nSee references/aria-patterns.md for complete patterns."
      },
      {
        "title": "Step 3: Implement Keyboard Navigation",
        "body": "All interactive elements must be keyboard-accessible:\n\n// Tab order management\nfunction Dialog({ onClose }) {\n  const dialogRef = useRef<HTMLDivElement>(null);\n  const previousFocus = useRef<HTMLElement | null>(null);\n\n  useEffect(() => {\n    // Save previous focus\n    previousFocus.current = document.activeElement as HTMLElement;\n\n    // Focus first element in dialog\n    const firstFocusable = dialogRef.current?.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])');\n    (firstFocusable as HTMLElement)?.focus();\n\n    // Trap focus within dialog\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (e.key === 'Escape') onClose();\n      if (e.key === 'Tab') {\n        // Focus trap logic here\n      }\n    };\n\n    document.addEventListener('keydown', handleKeyDown);\n    return () => {\n      document.removeEventListener('keydown', handleKeyDown);\n      // Restore focus on close\n      previousFocus.current?.focus();\n    };\n  }, [onClose]);\n\n  return <div ref={dialogRef} role=\"dialog\">...</div>;\n}\n\nEssential keyboard patterns:\n\nTab/Shift+Tab: Navigate between focusable elements\nEnter/Space: Activate buttons/links\nArrow keys: Navigate within components (tabs, menus)\nEscape: Close dialogs/menus\nHome/End: Jump to first/last item\n\nSee references/focus-management.md for complete patterns."
      },
      {
        "title": "Step 4: Ensure Color Contrast",
        "body": "WCAG AA requirements:\n\nNormal text (under 18pt): 4.5:1 contrast ratio\nLarge text (18pt+ or 14pt+ bold): 3:1 contrast ratio\nUI components (buttons, borders): 3:1 contrast ratio\n\n/* ❌ WRONG - insufficient contrast */\n:root {\n  --background: #ffffff;\n  --text: #999999;  /* 2.8:1 - fails WCAG AA */\n}\n\n/* ✅ CORRECT - sufficient contrast */\n:root {\n  --background: #ffffff;\n  --text: #595959;  /* 4.6:1 - passes WCAG AA */\n}\n\nTesting tools:\n\nBrowser DevTools (Chrome/Firefox have built-in checkers)\nContrast checker extensions\naxe DevTools extension\n\nSee references/color-contrast.md for complete guide."
      },
      {
        "title": "Step 5: Make Forms Accessible",
        "body": "Every form input needs a visible label:\n\n<!-- ❌ WRONG - placeholder is not a label -->\n<input type=\"email\" placeholder=\"Email address\">\n\n<!-- ✅ CORRECT - proper label -->\n<label for=\"email\">Email address</label>\n<input type=\"email\" id=\"email\" name=\"email\" required aria-required=\"true\">\n\nError handling:\n\n<label for=\"email\">Email address</label>\n<input\n  type=\"email\"\n  id=\"email\"\n  name=\"email\"\n  aria-invalid=\"true\"\n  aria-describedby=\"email-error\"\n>\n<span id=\"email-error\" role=\"alert\">\n  Please enter a valid email address\n</span>\n\nLive regions for dynamic errors:\n\n<div role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n  Form submission failed. Please fix the errors above.\n</div>\n\nSee references/forms-validation.md for complete patterns."
      },
      {
        "title": "Always Do",
        "body": "✅ Use semantic HTML elements first (button, a, nav, article, etc.)\n✅ Provide text alternatives for all non-text content\n✅ Ensure 4.5:1 contrast for normal text, 3:1 for large text/UI\n✅ Make all functionality keyboard accessible\n✅ Test with keyboard only (unplug mouse)\n✅ Test with screen reader (NVDA on Windows, VoiceOver on Mac)\n✅ Use proper heading hierarchy (h1 → h2 → h3, no skipping)\n✅ Label all form inputs with visible labels\n✅ Provide focus indicators (never just outline: none)\n✅ Use aria-live for dynamic content updates"
      },
      {
        "title": "Never Do",
        "body": "❌ Use div with onClick instead of button\n❌ Remove focus outlines without replacement\n❌ Use color alone to convey information\n❌ Use placeholders as labels\n❌ Skip heading levels (h1 → h3)\n❌ Use tabindex > 0 (messes with natural order)\n❌ Add ARIA when semantic HTML exists\n❌ Forget to restore focus after closing dialogs\n❌ Use role=\"presentation\" on focusable elements\n❌ Create keyboard traps (no way to escape)"
      },
      {
        "title": "Known Issues Prevention",
        "body": "This skill prevents 12 documented accessibility issues:"
      },
      {
        "title": "Issue #1: Missing Focus Indicators",
        "body": "Error: Interactive elements have no visible focus indicator\nSource: WCAG 2.4.7 (Focus Visible)\nWhy It Happens: CSS reset removes default outline\nPrevention: Always provide custom focus-visible styles"
      },
      {
        "title": "Issue #2: Insufficient Color Contrast",
        "body": "Error: Text has less than 4.5:1 contrast ratio\nSource: WCAG 1.4.3 (Contrast Minimum)\nWhy It Happens: Using light gray text on white background\nPrevention: Test all text colors with contrast checker"
      },
      {
        "title": "Issue #3: Missing Alt Text",
        "body": "Error: Images missing alt attributes\nSource: WCAG 1.1.1 (Non-text Content)\nWhy It Happens: Forgot to add or thought it was optional\nPrevention: Add alt=\"\" for decorative, descriptive alt for meaningful images"
      },
      {
        "title": "Issue #4: Keyboard Navigation Broken",
        "body": "Error: Interactive elements not reachable by keyboard\nSource: WCAG 2.1.1 (Keyboard)\nWhy It Happens: Using div onClick instead of button\nPrevention: Use semantic interactive elements (button, a)"
      },
      {
        "title": "Issue #5: Form Inputs Without Labels",
        "body": "Error: Input fields missing associated labels\nSource: WCAG 3.3.2 (Labels or Instructions)\nWhy It Happens: Using placeholder as label\nPrevention: Always use <label> element with for/id association"
      },
      {
        "title": "Issue #6: Skipped Heading Levels",
        "body": "Error: Heading hierarchy jumps from h1 to h3\nSource: WCAG 1.3.1 (Info and Relationships)\nWhy It Happens: Using headings for visual styling instead of semantics\nPrevention: Use headings in order, style with CSS"
      },
      {
        "title": "Issue #7: No Focus Trap in Dialogs",
        "body": "Error: Tab key exits dialog to background content\nSource: WCAG 2.4.3 (Focus Order)\nWhy It Happens: No focus trap implementation\nPrevention: Implement focus trap for modal dialogs"
      },
      {
        "title": "Issue #8: Missing aria-live for Dynamic Content",
        "body": "Error: Screen reader doesn't announce updates\nSource: WCAG 4.1.3 (Status Messages)\nWhy It Happens: Dynamic content added without announcement\nPrevention: Use aria-live=\"polite\" or \"assertive\""
      },
      {
        "title": "Issue #9: Color-Only Information",
        "body": "Error: Using only color to convey status\nSource: WCAG 1.4.1 (Use of Color)\nWhy It Happens: Red text for errors without icon/text\nPrevention: Add icon + text label, not just color"
      },
      {
        "title": "Issue #10: Non-descriptive Link Text",
        "body": "Error: Links with \"click here\" or \"read more\"\nSource: WCAG 2.4.4 (Link Purpose)\nWhy It Happens: Generic link text without context\nPrevention: Use descriptive link text or aria-label"
      },
      {
        "title": "Issue #11: Auto-playing Media",
        "body": "Error: Video/audio auto-plays without user control\nSource: WCAG 1.4.2 (Audio Control)\nWhy It Happens: Autoplay attribute without controls\nPrevention: Require user interaction to start media"
      },
      {
        "title": "Issue #12: Inaccessible Custom Controls",
        "body": "Error: Custom select/checkbox without keyboard support\nSource: WCAG 4.1.2 (Name, Role, Value)\nWhy It Happens: Building from divs without ARIA\nPrevention: Use native elements or implement full ARIA pattern"
      },
      {
        "title": "Perceivable",
        "body": "All images have alt text (or alt=\"\" if decorative)\n Text contrast ≥ 4.5:1 (normal), ≥ 3:1 (large)\n Color not used alone to convey information\n Text can be resized to 200% without loss of content\n No auto-playing audio >3 seconds"
      },
      {
        "title": "Operable",
        "body": "All functionality keyboard accessible\n No keyboard traps\n Visible focus indicators\n Users can pause/stop/hide moving content\n Page titles describe purpose\n Focus order is logical\n Link purpose clear from text or context\n Multiple ways to find pages (menu, search, sitemap)\n Headings and labels describe purpose"
      },
      {
        "title": "Understandable",
        "body": "Page language specified (<html lang=\"en\">)\n Language changes marked (<span lang=\"es\">)\n No unexpected context changes on focus/input\n Consistent navigation across site\n Form labels/instructions provided\n Input errors identified and described\n Error prevention for legal/financial/data changes"
      },
      {
        "title": "Robust",
        "body": "Valid HTML (no parsing errors)\n Name, role, value available for all UI components\n Status messages identified (aria-live)"
      },
      {
        "title": "1. Keyboard-Only Testing (5 minutes)",
        "body": "1. Unplug mouse or hide cursor\n2. Tab through entire page\n   - Can you reach all interactive elements?\n   - Can you activate all buttons/links?\n   - Is focus order logical?\n3. Use Enter/Space to activate\n4. Use Escape to close dialogs\n5. Use arrow keys in menus/tabs"
      },
      {
        "title": "2. Screen Reader Testing (10 minutes)",
        "body": "NVDA (Windows - Free):\n\nDownload: https://www.nvaccess.org/download/\nStart: Ctrl+Alt+N\nNavigate: Arrow keys or Tab\nRead: NVDA+Down arrow\nStop: NVDA+Q\n\nVoiceOver (Mac - Built-in):\n\nStart: Cmd+F5\nNavigate: VO+Right/Left arrow (VO = Ctrl+Option)\nRead: VO+A (read all)\nStop: Cmd+F5\n\nWhat to test:\n\nAre all interactive elements announced?\nAre images described properly?\nAre form labels read with inputs?\nAre dynamic updates announced?\nIs heading structure clear?"
      },
      {
        "title": "3. Automated Testing",
        "body": "axe DevTools (Browser extension - highly recommended):\n\nInstall: Chrome/Firefox extension\nRun: F12 → axe DevTools tab → Scan\nFix: Review violations, follow remediation\nRetest: Scan again after fixes\n\nLighthouse (Built into Chrome):\n\nOpen DevTools (F12)\nLighthouse tab\nSelect \"Accessibility\" category\nGenerate report\nScore 90+ is good, 100 is ideal"
      },
      {
        "title": "Pattern 1: Accessible Dialog/Modal",
        "body": "interface DialogProps {\n  isOpen: boolean;\n  onClose: () => void;\n  title: string;\n  children: React.ReactNode;\n}\n\nfunction Dialog({ isOpen, onClose, title, children }: DialogProps) {\n  const dialogRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    if (!isOpen) return;\n\n    const previousFocus = document.activeElement as HTMLElement;\n\n    // Focus first focusable element\n    const firstFocusable = dialogRef.current?.querySelector(\n      'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n    ) as HTMLElement;\n    firstFocusable?.focus();\n\n    // Focus trap\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (e.key === 'Escape') {\n        onClose();\n      }\n      if (e.key === 'Tab') {\n        const focusableElements = dialogRef.current?.querySelectorAll(\n          'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n        );\n        if (!focusableElements?.length) return;\n\n        const first = focusableElements[0] as HTMLElement;\n        const last = focusableElements[focusableElements.length - 1] as HTMLElement;\n\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\n    document.addEventListener('keydown', handleKeyDown);\n\n    return () => {\n      document.removeEventListener('keydown', handleKeyDown);\n      previousFocus?.focus();\n    };\n  }, [isOpen, onClose]);\n\n  if (!isOpen) return null;\n\n  return (\n    <>\n      {/* Backdrop */}\n      <div\n        className=\"dialog-backdrop\"\n        onClick={onClose}\n        aria-hidden=\"true\"\n      />\n\n      {/* Dialog */}\n      <div\n        ref={dialogRef}\n        role=\"dialog\"\n        aria-modal=\"true\"\n        aria-labelledby=\"dialog-title\"\n        className=\"dialog\"\n      >\n        <h2 id=\"dialog-title\">{title}</h2>\n        <div className=\"dialog-content\">{children}</div>\n        <button onClick={onClose} aria-label=\"Close dialog\">×</button>\n      </div>\n    </>\n  );\n}\n\nWhen to use: Any modal dialog or overlay that blocks interaction with background content."
      },
      {
        "title": "Pattern 2: Accessible Tabs",
        "body": "function Tabs({ tabs }: { tabs: Array<{ label: string; content: React.ReactNode }> }) {\n  const [activeIndex, setActiveIndex] = useState(0);\n\n  const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n    if (e.key === 'ArrowLeft') {\n      e.preventDefault();\n      const newIndex = index === 0 ? tabs.length - 1 : index - 1;\n      setActiveIndex(newIndex);\n    } else if (e.key === 'ArrowRight') {\n      e.preventDefault();\n      const newIndex = index === tabs.length - 1 ? 0 : index + 1;\n      setActiveIndex(newIndex);\n    } else if (e.key === 'Home') {\n      e.preventDefault();\n      setActiveIndex(0);\n    } else if (e.key === 'End') {\n      e.preventDefault();\n      setActiveIndex(tabs.length - 1);\n    }\n  };\n\n  return (\n    <div>\n      <div role=\"tablist\" aria-label=\"Content tabs\">\n        {tabs.map((tab, index) => (\n          <button\n            key={index}\n            role=\"tab\"\n            aria-selected={activeIndex === index}\n            aria-controls={`panel-${index}`}\n            id={`tab-${index}`}\n            tabIndex={activeIndex === index ? 0 : -1}\n            onClick={() => setActiveIndex(index)}\n            onKeyDown={(e) => handleKeyDown(e, index)}\n          >\n            {tab.label}\n          </button>\n        ))}\n      </div>\n      {tabs.map((tab, index) => (\n        <div\n          key={index}\n          role=\"tabpanel\"\n          id={`panel-${index}`}\n          aria-labelledby={`tab-${index}`}\n          hidden={activeIndex !== index}\n          tabIndex={0}\n        >\n          {tab.content}\n        </div>\n      ))}\n    </div>\n  );\n}\n\nWhen to use: Tabbed interface with multiple panels."
      },
      {
        "title": "Pattern 3: Skip Links",
        "body": "<!-- Place at very top of body -->\n<a href=\"#main-content\" class=\"skip-link\">\n  Skip to main content\n</a>\n\n<style>\n.skip-link {\n  position: absolute;\n  top: -40px;\n  left: 0;\n  background: var(--primary);\n  color: white;\n  padding: 8px 16px;\n  z-index: 9999;\n}\n\n.skip-link:focus {\n  top: 0;\n}\n</style>\n\n<!-- Then in your layout -->\n<main id=\"main-content\" tabindex=\"-1\">\n  <!-- Page content -->\n</main>\n\nWhen to use: All multi-page websites with navigation/header before main content."
      },
      {
        "title": "Pattern 4: Accessible Form with Validation",
        "body": "function ContactForm() {\n  const [errors, setErrors] = useState<Record<string, string>>({});\n  const [touched, setTouched] = useState<Record<string, boolean>>({});\n\n  const validateEmail = (email: string) => {\n    if (!email) return 'Email is required';\n    if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)) return 'Email is invalid';\n    return '';\n  };\n\n  const handleBlur = (field: string, value: string) => {\n    setTouched(prev => ({ ...prev, [field]: true }));\n    const error = validateEmail(value);\n    setErrors(prev => ({ ...prev, [field]: error }));\n  };\n\n  return (\n    <form>\n      <div>\n        <label htmlFor=\"email\">Email address *</label>\n        <input\n          type=\"email\"\n          id=\"email\"\n          name=\"email\"\n          required\n          aria-required=\"true\"\n          aria-invalid={touched.email && !!errors.email}\n          aria-describedby={errors.email ? 'email-error' : undefined}\n          onBlur={(e) => handleBlur('email', e.target.value)}\n        />\n        {touched.email && errors.email && (\n          <span id=\"email-error\" role=\"alert\" className=\"error\">\n            {errors.email}\n          </span>\n        )}\n      </div>\n\n      <button type=\"submit\">Submit</button>\n\n      {/* Global form error */}\n      <div role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n        {/* Dynamic error message appears here */}\n      </div>\n    </form>\n  );\n}\n\nWhen to use: All forms with validation."
      },
      {
        "title": "References (references/)",
        "body": "Detailed documentation for deep dives:\n\nwcag-checklist.md - Complete WCAG 2.1 Level A & AA requirements with examples\nsemantic-html.md - Element selection guide, when to use which tag\naria-patterns.md - ARIA roles, states, properties, and when to use them\nfocus-management.md - Focus order, focus traps, focus restoration patterns\ncolor-contrast.md - Contrast requirements, testing tools, color palette tips\nforms-validation.md - Accessible form patterns, error handling, announcements\n\nWhen Claude should load these:\n\nUser asks for complete WCAG checklist\nDeep dive into specific pattern (tabs, accordions, etc.)\nColor contrast issues or palette design\nComplex form validation scenarios"
      },
      {
        "title": "Agents (agents/)",
        "body": "a11y-auditor.md - Automated accessibility auditor that checks pages for violations\n\nWhen to use: Request accessibility audit of existing page/component."
      },
      {
        "title": "ARIA Live Regions",
        "body": "Three politeness levels:\n\n<!-- Polite: Wait for screen reader to finish current announcement -->\n<div aria-live=\"polite\">New messages: 3</div>\n\n<!-- Assertive: Interrupt immediately -->\n<div aria-live=\"assertive\" role=\"alert\">\n  Error: Form submission failed\n</div>\n\n<!-- Off: Don't announce (default) -->\n<div aria-live=\"off\">Loading...</div>\n\nBest practices:\n\nUse polite for non-critical updates (notifications, counters)\nUse assertive for errors and critical alerts\nUse aria-atomic=\"true\" to read entire region on change\nKeep messages concise and meaningful"
      },
      {
        "title": "Focus Management in SPAs",
        "body": "React Router doesn't reset focus on navigation - you need to handle it:\n\nfunction App() {\n  const location = useLocation();\n  const mainRef = useRef<HTMLElement>(null);\n\n  useEffect(() => {\n    // Focus main content on route change\n    mainRef.current?.focus();\n    // Announce page title to screen readers\n    const title = document.title;\n    const announcement = document.createElement('div');\n    announcement.setAttribute('role', 'status');\n    announcement.setAttribute('aria-live', 'polite');\n    announcement.textContent = `Navigated to ${title}`;\n    document.body.appendChild(announcement);\n    setTimeout(() => announcement.remove(), 1000);\n  }, [location.pathname]);\n\n  return <main ref={mainRef} tabIndex={-1} id=\"main-content\">...</main>;\n}"
      },
      {
        "title": "Accessible Data Tables",
        "body": "<table>\n  <caption>Monthly sales by region</caption>\n  <thead>\n    <tr>\n      <th scope=\"col\">Region</th>\n      <th scope=\"col\">Q1</th>\n      <th scope=\"col\">Q2</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th scope=\"row\">North</th>\n      <td>$10,000</td>\n      <td>$12,000</td>\n    </tr>\n  </tbody>\n</table>\n\nKey attributes:\n\n<caption> - Describes table purpose\nscope=\"col\" - Identifies column headers\nscope=\"row\" - Identifies row headers\nAssociates data cells with headers for screen readers"
      },
      {
        "title": "Official Documentation",
        "body": "WCAG 2.1: https://www.w3.org/WAI/WCAG21/quickref/\nMDN Accessibility: https://developer.mozilla.org/en-US/docs/Web/Accessibility\nARIA Authoring Practices: https://www.w3.org/WAI/ARIA/apg/\nWebAIM: https://webaim.org/articles/\naxe DevTools: https://www.deque.com/axe/devtools/"
      },
      {
        "title": "Problem: Focus indicators not visible",
        "body": "Symptoms: Can tab through page but don't see where focus is\nCause: CSS removed outlines or insufficient contrast\nSolution:\n\n*:focus-visible {\n  outline: 2px solid var(--primary);\n  outline-offset: 2px;\n}"
      },
      {
        "title": "Problem: Screen reader not announcing updates",
        "body": "Symptoms: Dynamic content changes but no announcement\nCause: No aria-live region\nSolution: Wrap dynamic content in <div aria-live=\"polite\"> or use role=\"alert\""
      },
      {
        "title": "Problem: Dialog focus escapes to background",
        "body": "Symptoms: Tab key navigates to elements behind dialog\nCause: No focus trap\nSolution: Implement focus trap (see Pattern 1 above)"
      },
      {
        "title": "Problem: Form errors not announced",
        "body": "Symptoms: Visual errors appear but screen reader doesn't notice\nCause: No aria-invalid or role=\"alert\"\nSolution: Use aria-invalid + aria-describedby pointing to error message with role=\"alert\""
      },
      {
        "title": "Complete Setup Checklist",
        "body": "Use this for every page/component:\n\nAll interactive elements are keyboard accessible\n Visible focus indicators on all focusable elements\n Images have alt text (or alt=\"\" if decorative)\n Text contrast ≥ 4.5:1 (test with axe or Lighthouse)\n Form inputs have associated labels (not just placeholders)\n Heading hierarchy is logical (no skipped levels)\n Page has <html lang=\"en\"> or appropriate language\n Dialogs have focus trap and restore focus on close\n Dynamic content uses aria-live or role=\"alert\"\n Color not used alone to convey information\n Tested with keyboard only (no mouse)\n Tested with screen reader (NVDA or VoiceOver)\n Ran axe DevTools scan (0 violations)\n Lighthouse accessibility score ≥ 90\n\nQuestions? Issues?\n\nCheck references/wcag-checklist.md for complete requirements\nUse /a11y-auditor agent to scan your page\nRun axe DevTools for automated testing\nTest with actual keyboard + screen reader\n\nStandards: WCAG 2.1 Level AA\nTesting Tools: axe DevTools, Lighthouse, NVDA, VoiceOver\nSuccess Criteria: 90+ Lighthouse score, 0 critical violations"
      }
    ],
    "body": "Web Accessibility (WCAG 2.1 AA)\n\nStatus: Production Ready ✅ Last Updated: 2026-01-14 Dependencies: None (framework-agnostic) Standards: WCAG 2.1 Level AA\n\nQuick Start (5 Minutes)\n1. Semantic HTML Foundation\n\nChoose the right element - don't use div for everything:\n\n<!-- ❌ WRONG - divs with onClick -->\n<div onclick=\"submit()\">Submit</div>\n<div onclick=\"navigate()\">Next page</div>\n\n<!-- ✅ CORRECT - semantic elements -->\n<button type=\"submit\">Submit</button>\n<a href=\"/next\">Next page</a>\n\n\nWhy this matters:\n\nSemantic elements have built-in keyboard support\nScreen readers announce role automatically\nBrowser provides default accessible behaviors\n2. Focus Management\n\nMake interactive elements keyboard-accessible:\n\n/* ❌ WRONG - removes focus outline */\nbutton:focus { outline: none; }\n\n/* ✅ CORRECT - custom accessible outline */\nbutton:focus-visible {\n  outline: 2px solid var(--primary);\n  outline-offset: 2px;\n}\n\n\nCRITICAL:\n\nNever remove focus outlines without replacement\nUse :focus-visible to show only on keyboard focus\nEnsure 3:1 contrast ratio for focus indicators\n3. Text Alternatives\n\nEvery non-text element needs a text alternative:\n\n<!-- ❌ WRONG - no alt text -->\n<img src=\"logo.png\">\n<button><svg>...</svg></button>\n\n<!-- ✅ CORRECT - proper alternatives -->\n<img src=\"logo.png\" alt=\"Company Name\">\n<button aria-label=\"Close dialog\"><svg>...</svg></button>\n\nThe 5-Step Accessibility Process\nStep 1: Choose Semantic HTML\n\nDecision tree for element selection:\n\nNeed clickable element?\n├─ Navigates to another page? → <a href=\"...\">\n├─ Submits form? → <button type=\"submit\">\n├─ Opens dialog? → <button aria-haspopup=\"dialog\">\n└─ Other action? → <button type=\"button\">\n\nGrouping content?\n├─ Self-contained article? → <article>\n├─ Thematic section? → <section>\n├─ Navigation links? → <nav>\n└─ Supplementary info? → <aside>\n\nForm element?\n├─ Text input? → <input type=\"text\">\n├─ Multiple choice? → <select> or <input type=\"radio\">\n├─ Toggle? → <input type=\"checkbox\"> or <button aria-pressed>\n└─ Long text? → <textarea>\n\n\nSee references/semantic-html.md for complete guide.\n\nStep 2: Add ARIA When Needed\n\nGolden rule: Use ARIA only when HTML can't express the pattern.\n\n<!-- ❌ WRONG - unnecessary ARIA -->\n<button role=\"button\">Click me</button>  <!-- Button already has role -->\n\n<!-- ✅ CORRECT - ARIA fills semantic gap -->\n<div role=\"dialog\" aria-labelledby=\"title\" aria-modal=\"true\">\n  <h2 id=\"title\">Confirm action</h2>\n  <!-- No HTML dialog yet, so role needed -->\n</div>\n\n<!-- ✅ BETTER - Use native HTML when available -->\n<dialog aria-labelledby=\"title\">\n  <h2 id=\"title\">Confirm action</h2>\n</dialog>\n\n\nCommon ARIA patterns:\n\naria-label - When visible label doesn't exist\naria-labelledby - Reference existing text as label\naria-describedby - Additional description\naria-live - Announce dynamic updates\naria-expanded - Collapsible/expandable state\n\nSee references/aria-patterns.md for complete patterns.\n\nStep 3: Implement Keyboard Navigation\n\nAll interactive elements must be keyboard-accessible:\n\n// Tab order management\nfunction Dialog({ onClose }) {\n  const dialogRef = useRef<HTMLDivElement>(null);\n  const previousFocus = useRef<HTMLElement | null>(null);\n\n  useEffect(() => {\n    // Save previous focus\n    previousFocus.current = document.activeElement as HTMLElement;\n\n    // Focus first element in dialog\n    const firstFocusable = dialogRef.current?.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])');\n    (firstFocusable as HTMLElement)?.focus();\n\n    // Trap focus within dialog\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (e.key === 'Escape') onClose();\n      if (e.key === 'Tab') {\n        // Focus trap logic here\n      }\n    };\n\n    document.addEventListener('keydown', handleKeyDown);\n    return () => {\n      document.removeEventListener('keydown', handleKeyDown);\n      // Restore focus on close\n      previousFocus.current?.focus();\n    };\n  }, [onClose]);\n\n  return <div ref={dialogRef} role=\"dialog\">...</div>;\n}\n\n\nEssential keyboard patterns:\n\nTab/Shift+Tab: Navigate between focusable elements\nEnter/Space: Activate buttons/links\nArrow keys: Navigate within components (tabs, menus)\nEscape: Close dialogs/menus\nHome/End: Jump to first/last item\n\nSee references/focus-management.md for complete patterns.\n\nStep 4: Ensure Color Contrast\n\nWCAG AA requirements:\n\nNormal text (under 18pt): 4.5:1 contrast ratio\nLarge text (18pt+ or 14pt+ bold): 3:1 contrast ratio\nUI components (buttons, borders): 3:1 contrast ratio\n/* ❌ WRONG - insufficient contrast */\n:root {\n  --background: #ffffff;\n  --text: #999999;  /* 2.8:1 - fails WCAG AA */\n}\n\n/* ✅ CORRECT - sufficient contrast */\n:root {\n  --background: #ffffff;\n  --text: #595959;  /* 4.6:1 - passes WCAG AA */\n}\n\n\nTesting tools:\n\nBrowser DevTools (Chrome/Firefox have built-in checkers)\nContrast checker extensions\naxe DevTools extension\n\nSee references/color-contrast.md for complete guide.\n\nStep 5: Make Forms Accessible\n\nEvery form input needs a visible label:\n\n<!-- ❌ WRONG - placeholder is not a label -->\n<input type=\"email\" placeholder=\"Email address\">\n\n<!-- ✅ CORRECT - proper label -->\n<label for=\"email\">Email address</label>\n<input type=\"email\" id=\"email\" name=\"email\" required aria-required=\"true\">\n\n\nError handling:\n\n<label for=\"email\">Email address</label>\n<input\n  type=\"email\"\n  id=\"email\"\n  name=\"email\"\n  aria-invalid=\"true\"\n  aria-describedby=\"email-error\"\n>\n<span id=\"email-error\" role=\"alert\">\n  Please enter a valid email address\n</span>\n\n\nLive regions for dynamic errors:\n\n<div role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n  Form submission failed. Please fix the errors above.\n</div>\n\n\nSee references/forms-validation.md for complete patterns.\n\nCritical Rules\nAlways Do\n\n✅ Use semantic HTML elements first (button, a, nav, article, etc.) ✅ Provide text alternatives for all non-text content ✅ Ensure 4.5:1 contrast for normal text, 3:1 for large text/UI ✅ Make all functionality keyboard accessible ✅ Test with keyboard only (unplug mouse) ✅ Test with screen reader (NVDA on Windows, VoiceOver on Mac) ✅ Use proper heading hierarchy (h1 → h2 → h3, no skipping) ✅ Label all form inputs with visible labels ✅ Provide focus indicators (never just outline: none) ✅ Use aria-live for dynamic content updates\n\nNever Do\n\n❌ Use div with onClick instead of button ❌ Remove focus outlines without replacement ❌ Use color alone to convey information ❌ Use placeholders as labels ❌ Skip heading levels (h1 → h3) ❌ Use tabindex > 0 (messes with natural order) ❌ Add ARIA when semantic HTML exists ❌ Forget to restore focus after closing dialogs ❌ Use role=\"presentation\" on focusable elements ❌ Create keyboard traps (no way to escape)\n\nKnown Issues Prevention\n\nThis skill prevents 12 documented accessibility issues:\n\nIssue #1: Missing Focus Indicators\n\nError: Interactive elements have no visible focus indicator Source: WCAG 2.4.7 (Focus Visible) Why It Happens: CSS reset removes default outline Prevention: Always provide custom focus-visible styles\n\nIssue #2: Insufficient Color Contrast\n\nError: Text has less than 4.5:1 contrast ratio Source: WCAG 1.4.3 (Contrast Minimum) Why It Happens: Using light gray text on white background Prevention: Test all text colors with contrast checker\n\nIssue #3: Missing Alt Text\n\nError: Images missing alt attributes Source: WCAG 1.1.1 (Non-text Content) Why It Happens: Forgot to add or thought it was optional Prevention: Add alt=\"\" for decorative, descriptive alt for meaningful images\n\nIssue #4: Keyboard Navigation Broken\n\nError: Interactive elements not reachable by keyboard Source: WCAG 2.1.1 (Keyboard) Why It Happens: Using div onClick instead of button Prevention: Use semantic interactive elements (button, a)\n\nIssue #5: Form Inputs Without Labels\n\nError: Input fields missing associated labels Source: WCAG 3.3.2 (Labels or Instructions) Why It Happens: Using placeholder as label Prevention: Always use <label> element with for/id association\n\nIssue #6: Skipped Heading Levels\n\nError: Heading hierarchy jumps from h1 to h3 Source: WCAG 1.3.1 (Info and Relationships) Why It Happens: Using headings for visual styling instead of semantics Prevention: Use headings in order, style with CSS\n\nIssue #7: No Focus Trap in Dialogs\n\nError: Tab key exits dialog to background content Source: WCAG 2.4.3 (Focus Order) Why It Happens: No focus trap implementation Prevention: Implement focus trap for modal dialogs\n\nIssue #8: Missing aria-live for Dynamic Content\n\nError: Screen reader doesn't announce updates Source: WCAG 4.1.3 (Status Messages) Why It Happens: Dynamic content added without announcement Prevention: Use aria-live=\"polite\" or \"assertive\"\n\nIssue #9: Color-Only Information\n\nError: Using only color to convey status Source: WCAG 1.4.1 (Use of Color) Why It Happens: Red text for errors without icon/text Prevention: Add icon + text label, not just color\n\nIssue #10: Non-descriptive Link Text\n\nError: Links with \"click here\" or \"read more\" Source: WCAG 2.4.4 (Link Purpose) Why It Happens: Generic link text without context Prevention: Use descriptive link text or aria-label\n\nIssue #11: Auto-playing Media\n\nError: Video/audio auto-plays without user control Source: WCAG 1.4.2 (Audio Control) Why It Happens: Autoplay attribute without controls Prevention: Require user interaction to start media\n\nIssue #12: Inaccessible Custom Controls\n\nError: Custom select/checkbox without keyboard support Source: WCAG 4.1.2 (Name, Role, Value) Why It Happens: Building from divs without ARIA Prevention: Use native elements or implement full ARIA pattern\n\nWCAG 2.1 AA Quick Checklist\nPerceivable\n All images have alt text (or alt=\"\" if decorative)\n Text contrast ≥ 4.5:1 (normal), ≥ 3:1 (large)\n Color not used alone to convey information\n Text can be resized to 200% without loss of content\n No auto-playing audio >3 seconds\nOperable\n All functionality keyboard accessible\n No keyboard traps\n Visible focus indicators\n Users can pause/stop/hide moving content\n Page titles describe purpose\n Focus order is logical\n Link purpose clear from text or context\n Multiple ways to find pages (menu, search, sitemap)\n Headings and labels describe purpose\nUnderstandable\n Page language specified (<html lang=\"en\">)\n Language changes marked (<span lang=\"es\">)\n No unexpected context changes on focus/input\n Consistent navigation across site\n Form labels/instructions provided\n Input errors identified and described\n Error prevention for legal/financial/data changes\nRobust\n Valid HTML (no parsing errors)\n Name, role, value available for all UI components\n Status messages identified (aria-live)\nTesting Workflow\n1. Keyboard-Only Testing (5 minutes)\n1. Unplug mouse or hide cursor\n2. Tab through entire page\n   - Can you reach all interactive elements?\n   - Can you activate all buttons/links?\n   - Is focus order logical?\n3. Use Enter/Space to activate\n4. Use Escape to close dialogs\n5. Use arrow keys in menus/tabs\n\n2. Screen Reader Testing (10 minutes)\n\nNVDA (Windows - Free):\n\nDownload: https://www.nvaccess.org/download/\nStart: Ctrl+Alt+N\nNavigate: Arrow keys or Tab\nRead: NVDA+Down arrow\nStop: NVDA+Q\n\nVoiceOver (Mac - Built-in):\n\nStart: Cmd+F5\nNavigate: VO+Right/Left arrow (VO = Ctrl+Option)\nRead: VO+A (read all)\nStop: Cmd+F5\n\nWhat to test:\n\nAre all interactive elements announced?\nAre images described properly?\nAre form labels read with inputs?\nAre dynamic updates announced?\nIs heading structure clear?\n3. Automated Testing\n\naxe DevTools (Browser extension - highly recommended):\n\nInstall: Chrome/Firefox extension\nRun: F12 → axe DevTools tab → Scan\nFix: Review violations, follow remediation\nRetest: Scan again after fixes\n\nLighthouse (Built into Chrome):\n\nOpen DevTools (F12)\nLighthouse tab\nSelect \"Accessibility\" category\nGenerate report\nScore 90+ is good, 100 is ideal\nCommon Patterns\nPattern 1: Accessible Dialog/Modal\ninterface DialogProps {\n  isOpen: boolean;\n  onClose: () => void;\n  title: string;\n  children: React.ReactNode;\n}\n\nfunction Dialog({ isOpen, onClose, title, children }: DialogProps) {\n  const dialogRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    if (!isOpen) return;\n\n    const previousFocus = document.activeElement as HTMLElement;\n\n    // Focus first focusable element\n    const firstFocusable = dialogRef.current?.querySelector(\n      'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n    ) as HTMLElement;\n    firstFocusable?.focus();\n\n    // Focus trap\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (e.key === 'Escape') {\n        onClose();\n      }\n      if (e.key === 'Tab') {\n        const focusableElements = dialogRef.current?.querySelectorAll(\n          'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n        );\n        if (!focusableElements?.length) return;\n\n        const first = focusableElements[0] as HTMLElement;\n        const last = focusableElements[focusableElements.length - 1] as HTMLElement;\n\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\n    document.addEventListener('keydown', handleKeyDown);\n\n    return () => {\n      document.removeEventListener('keydown', handleKeyDown);\n      previousFocus?.focus();\n    };\n  }, [isOpen, onClose]);\n\n  if (!isOpen) return null;\n\n  return (\n    <>\n      {/* Backdrop */}\n      <div\n        className=\"dialog-backdrop\"\n        onClick={onClose}\n        aria-hidden=\"true\"\n      />\n\n      {/* Dialog */}\n      <div\n        ref={dialogRef}\n        role=\"dialog\"\n        aria-modal=\"true\"\n        aria-labelledby=\"dialog-title\"\n        className=\"dialog\"\n      >\n        <h2 id=\"dialog-title\">{title}</h2>\n        <div className=\"dialog-content\">{children}</div>\n        <button onClick={onClose} aria-label=\"Close dialog\">×</button>\n      </div>\n    </>\n  );\n}\n\n\nWhen to use: Any modal dialog or overlay that blocks interaction with background content.\n\nPattern 2: Accessible Tabs\nfunction Tabs({ tabs }: { tabs: Array<{ label: string; content: React.ReactNode }> }) {\n  const [activeIndex, setActiveIndex] = useState(0);\n\n  const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n    if (e.key === 'ArrowLeft') {\n      e.preventDefault();\n      const newIndex = index === 0 ? tabs.length - 1 : index - 1;\n      setActiveIndex(newIndex);\n    } else if (e.key === 'ArrowRight') {\n      e.preventDefault();\n      const newIndex = index === tabs.length - 1 ? 0 : index + 1;\n      setActiveIndex(newIndex);\n    } else if (e.key === 'Home') {\n      e.preventDefault();\n      setActiveIndex(0);\n    } else if (e.key === 'End') {\n      e.preventDefault();\n      setActiveIndex(tabs.length - 1);\n    }\n  };\n\n  return (\n    <div>\n      <div role=\"tablist\" aria-label=\"Content tabs\">\n        {tabs.map((tab, index) => (\n          <button\n            key={index}\n            role=\"tab\"\n            aria-selected={activeIndex === index}\n            aria-controls={`panel-${index}`}\n            id={`tab-${index}`}\n            tabIndex={activeIndex === index ? 0 : -1}\n            onClick={() => setActiveIndex(index)}\n            onKeyDown={(e) => handleKeyDown(e, index)}\n          >\n            {tab.label}\n          </button>\n        ))}\n      </div>\n      {tabs.map((tab, index) => (\n        <div\n          key={index}\n          role=\"tabpanel\"\n          id={`panel-${index}`}\n          aria-labelledby={`tab-${index}`}\n          hidden={activeIndex !== index}\n          tabIndex={0}\n        >\n          {tab.content}\n        </div>\n      ))}\n    </div>\n  );\n}\n\n\nWhen to use: Tabbed interface with multiple panels.\n\nPattern 3: Skip Links\n<!-- Place at very top of body -->\n<a href=\"#main-content\" class=\"skip-link\">\n  Skip to main content\n</a>\n\n<style>\n.skip-link {\n  position: absolute;\n  top: -40px;\n  left: 0;\n  background: var(--primary);\n  color: white;\n  padding: 8px 16px;\n  z-index: 9999;\n}\n\n.skip-link:focus {\n  top: 0;\n}\n</style>\n\n<!-- Then in your layout -->\n<main id=\"main-content\" tabindex=\"-1\">\n  <!-- Page content -->\n</main>\n\n\nWhen to use: All multi-page websites with navigation/header before main content.\n\nPattern 4: Accessible Form with Validation\nfunction ContactForm() {\n  const [errors, setErrors] = useState<Record<string, string>>({});\n  const [touched, setTouched] = useState<Record<string, boolean>>({});\n\n  const validateEmail = (email: string) => {\n    if (!email) return 'Email is required';\n    if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)) return 'Email is invalid';\n    return '';\n  };\n\n  const handleBlur = (field: string, value: string) => {\n    setTouched(prev => ({ ...prev, [field]: true }));\n    const error = validateEmail(value);\n    setErrors(prev => ({ ...prev, [field]: error }));\n  };\n\n  return (\n    <form>\n      <div>\n        <label htmlFor=\"email\">Email address *</label>\n        <input\n          type=\"email\"\n          id=\"email\"\n          name=\"email\"\n          required\n          aria-required=\"true\"\n          aria-invalid={touched.email && !!errors.email}\n          aria-describedby={errors.email ? 'email-error' : undefined}\n          onBlur={(e) => handleBlur('email', e.target.value)}\n        />\n        {touched.email && errors.email && (\n          <span id=\"email-error\" role=\"alert\" className=\"error\">\n            {errors.email}\n          </span>\n        )}\n      </div>\n\n      <button type=\"submit\">Submit</button>\n\n      {/* Global form error */}\n      <div role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n        {/* Dynamic error message appears here */}\n      </div>\n    </form>\n  );\n}\n\n\nWhen to use: All forms with validation.\n\nUsing Bundled Resources\nReferences (references/)\n\nDetailed documentation for deep dives:\n\nwcag-checklist.md - Complete WCAG 2.1 Level A & AA requirements with examples\nsemantic-html.md - Element selection guide, when to use which tag\naria-patterns.md - ARIA roles, states, properties, and when to use them\nfocus-management.md - Focus order, focus traps, focus restoration patterns\ncolor-contrast.md - Contrast requirements, testing tools, color palette tips\nforms-validation.md - Accessible form patterns, error handling, announcements\n\nWhen Claude should load these:\n\nUser asks for complete WCAG checklist\nDeep dive into specific pattern (tabs, accordions, etc.)\nColor contrast issues or palette design\nComplex form validation scenarios\nAgents (agents/)\na11y-auditor.md - Automated accessibility auditor that checks pages for violations\n\nWhen to use: Request accessibility audit of existing page/component.\n\nAdvanced Topics\nARIA Live Regions\n\nThree politeness levels:\n\n<!-- Polite: Wait for screen reader to finish current announcement -->\n<div aria-live=\"polite\">New messages: 3</div>\n\n<!-- Assertive: Interrupt immediately -->\n<div aria-live=\"assertive\" role=\"alert\">\n  Error: Form submission failed\n</div>\n\n<!-- Off: Don't announce (default) -->\n<div aria-live=\"off\">Loading...</div>\n\n\nBest practices:\n\nUse polite for non-critical updates (notifications, counters)\nUse assertive for errors and critical alerts\nUse aria-atomic=\"true\" to read entire region on change\nKeep messages concise and meaningful\nFocus Management in SPAs\n\nReact Router doesn't reset focus on navigation - you need to handle it:\n\nfunction App() {\n  const location = useLocation();\n  const mainRef = useRef<HTMLElement>(null);\n\n  useEffect(() => {\n    // Focus main content on route change\n    mainRef.current?.focus();\n    // Announce page title to screen readers\n    const title = document.title;\n    const announcement = document.createElement('div');\n    announcement.setAttribute('role', 'status');\n    announcement.setAttribute('aria-live', 'polite');\n    announcement.textContent = `Navigated to ${title}`;\n    document.body.appendChild(announcement);\n    setTimeout(() => announcement.remove(), 1000);\n  }, [location.pathname]);\n\n  return <main ref={mainRef} tabIndex={-1} id=\"main-content\">...</main>;\n}\n\nAccessible Data Tables\n<table>\n  <caption>Monthly sales by region</caption>\n  <thead>\n    <tr>\n      <th scope=\"col\">Region</th>\n      <th scope=\"col\">Q1</th>\n      <th scope=\"col\">Q2</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th scope=\"row\">North</th>\n      <td>$10,000</td>\n      <td>$12,000</td>\n    </tr>\n  </tbody>\n</table>\n\n\nKey attributes:\n\n<caption> - Describes table purpose\nscope=\"col\" - Identifies column headers\nscope=\"row\" - Identifies row headers\nAssociates data cells with headers for screen readers\nOfficial Documentation\nWCAG 2.1: https://www.w3.org/WAI/WCAG21/quickref/\nMDN Accessibility: https://developer.mozilla.org/en-US/docs/Web/Accessibility\nARIA Authoring Practices: https://www.w3.org/WAI/ARIA/apg/\nWebAIM: https://webaim.org/articles/\naxe DevTools: https://www.deque.com/axe/devtools/\nTroubleshooting\nProblem: Focus indicators not visible\n\nSymptoms: Can tab through page but don't see where focus is Cause: CSS removed outlines or insufficient contrast Solution:\n\n*:focus-visible {\n  outline: 2px solid var(--primary);\n  outline-offset: 2px;\n}\n\nProblem: Screen reader not announcing updates\n\nSymptoms: Dynamic content changes but no announcement Cause: No aria-live region Solution: Wrap dynamic content in <div aria-live=\"polite\"> or use role=\"alert\"\n\nProblem: Dialog focus escapes to background\n\nSymptoms: Tab key navigates to elements behind dialog Cause: No focus trap Solution: Implement focus trap (see Pattern 1 above)\n\nProblem: Form errors not announced\n\nSymptoms: Visual errors appear but screen reader doesn't notice Cause: No aria-invalid or role=\"alert\" Solution: Use aria-invalid + aria-describedby pointing to error message with role=\"alert\"\n\nComplete Setup Checklist\n\nUse this for every page/component:\n\n All interactive elements are keyboard accessible\n Visible focus indicators on all focusable elements\n Images have alt text (or alt=\"\" if decorative)\n Text contrast ≥ 4.5:1 (test with axe or Lighthouse)\n Form inputs have associated labels (not just placeholders)\n Heading hierarchy is logical (no skipped levels)\n Page has <html lang=\"en\"> or appropriate language\n Dialogs have focus trap and restore focus on close\n Dynamic content uses aria-live or role=\"alert\"\n Color not used alone to convey information\n Tested with keyboard only (no mouse)\n Tested with screen reader (NVDA or VoiceOver)\n Ran axe DevTools scan (0 violations)\n Lighthouse accessibility score ≥ 90\n\nQuestions? Issues?\n\nCheck references/wcag-checklist.md for complete requirements\nUse /a11y-auditor agent to scan your page\nRun axe DevTools for automated testing\nTest with actual keyboard + screen reader\n\nStandards: WCAG 2.1 Level AA Testing Tools: axe DevTools, Lighthouse, NVDA, VoiceOver Success Criteria: 90+ Lighthouse score, 0 critical violations"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/Veeramanikandanr48/accessibility",
    "publisherUrl": "https://clawhub.ai/Veeramanikandanr48/accessibility",
    "owner": "Veeramanikandanr48",
    "version": "0.1.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/accessibility",
    "downloadUrl": "https://openagent3.xyz/downloads/accessibility",
    "agentUrl": "https://openagent3.xyz/skills/accessibility/agent",
    "manifestUrl": "https://openagent3.xyz/skills/accessibility/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/accessibility/agent.md"
  }
}