[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6Q31ZMEVR1CNJ2Y813KT84A":3,"skill-same:01M6Q31ZMEVR1CNJ2Y813KT84A":115},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":22,"publishedAt":42,"linkOnly":43,"markdownContent":44,"markdownBody":45,"frontMatter":46,"refName":47,"commitSha":48,"skillDirectory":49,"forksCount":50,"repositoryHtmlUrl":51,"repositoryHomepageUrl":6,"ownerHtmlUrl":52,"ownerBlogUrl":6,"ownerDescription":53,"updatedAt":54,"install":55,"resources":68},"01M6Q31ZMEVR1CNJ2Y813KT84A","accessibility",null,"Build WCAG 2.1 AA compliant websites with semantic HTML, proper ARIA, focus management, and screen reader support. Includes color contrast (4.5:1 text), keyboard navigation, form labels, and live regions.\n\nUse when implementing accessible interfaces, fixing screen reader issues, keyboard navigation, or troubleshooting \"focus outline missing\", \"aria-label required\", \"insufficient contrast\".","Kernel8901\u002Fai-agent-skills-classification","Kernel8901","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F72647553?v=4","01M6KV388J2H5PNC8D8P940BJD","01M6KV38CS5B3AY63RKZB410YM",7,"MIT","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F网页与前端开发\u002Faccessibility\u002FSKILL.md",[17],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",[5,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41],"labels","support","implementing","contrast","with","color","form","missing","screen","keyboard","live","focus","outline","aria","html","use","text","WCAG","issues","2026-11-10 10:12:30",false,"---\nname: accessibility\ndescription: |\n  Build WCAG 2.1 AA compliant websites with semantic HTML, proper ARIA, focus management, and screen reader support. Includes color contrast (4.5:1 text), keyboard navigation, form labels, and live regions.\n\n  Use when implementing accessible interfaces, fixing screen reader issues, keyboard navigation, or troubleshooting \"focus outline missing\", \"aria-label required\", \"insufficient contrast\".\n---\n\n# Web Accessibility (WCAG 2.1 AA)\n\n**Status**: Production Ready ✅\n**Last Updated**: 2026-01-14\n**Dependencies**: None (framework-agnostic)\n**Standards**: WCAG 2.1 Level AA\n\n---\n\n## Quick Start (5 Minutes)\n\n### 1. Semantic HTML Foundation\n\nChoose the right element - don't use `div` for everything:\n\n```html\n\u003C!-- ❌ WRONG - divs with onClick -->\n\u003Cdiv onclick=\"submit()\">Submit\u003C\u002Fdiv>\n\u003Cdiv onclick=\"navigate()\">Next page\u003C\u002Fdiv>\n\n\u003C!-- ✅ CORRECT - semantic elements -->\n\u003Cbutton type=\"submit\">Submit\u003C\u002Fbutton>\n\u003Ca href=\"\u002Fnext\">Next page\u003C\u002Fa>\n```\n\n**Why this matters:**\n- Semantic elements have built-in keyboard support\n- Screen readers announce role automatically\n- Browser provides default accessible behaviors\n\n### 2. Focus Management\n\nMake interactive elements keyboard-accessible:\n\n```css\n\u002F* ❌ WRONG - removes focus outline *\u002F\nbutton:focus { outline: none; }\n\n\u002F* ✅ CORRECT - custom accessible outline *\u002F\nbutton:focus-visible {\n  outline: 2px solid var(--primary);\n  outline-offset: 2px;\n}\n```\n\n**CRITICAL:**\n- Never remove focus outlines without replacement\n- Use `:focus-visible` to show only on keyboard focus\n- Ensure 3:1 contrast ratio for focus indicators\n\n### 3. Text Alternatives\n\nEvery non-text element needs a text alternative:\n\n```html\n\u003C!-- ❌ WRONG - no alt text -->\n\u003Cimg src=\"logo.png\">\n\u003Cbutton>\u003Csvg>...\u003C\u002Fsvg>\u003C\u002Fbutton>\n\n\u003C!-- ✅ CORRECT - proper alternatives -->\n\u003Cimg src=\"logo.png\" alt=\"Company Name\">\n\u003Cbutton aria-label=\"Close dialog\">\u003Csvg>...\u003C\u002Fsvg>\u003C\u002Fbutton>\n```\n\n---\n\n## The 5-Step Accessibility Process\n\n### Step 1: Choose Semantic HTML\n\n**Decision tree for element selection:**\n\n```\nNeed clickable element?\n├─ Navigates to another page? → \u003Ca href=\"...\">\n├─ Submits form? → \u003Cbutton type=\"submit\">\n├─ Opens dialog? → \u003Cbutton aria-haspopup=\"dialog\">\n└─ Other action? → \u003Cbutton type=\"button\">\n\nGrouping content?\n├─ Self-contained article? → \u003Carticle>\n├─ Thematic section? → \u003Csection>\n├─ Navigation links? → \u003Cnav>\n└─ Supplementary info? → \u003Caside>\n\nForm element?\n├─ Text input? → \u003Cinput type=\"text\">\n├─ Multiple choice? → \u003Cselect> or \u003Cinput type=\"radio\">\n├─ Toggle? → \u003Cinput type=\"checkbox\"> or \u003Cbutton aria-pressed>\n└─ Long text? → \u003Ctextarea>\n```\n\n**See `references\u002Fsemantic-html.md` for complete guide.**\n\n### Step 2: Add ARIA When Needed\n\n**Golden rule: Use ARIA only when HTML can't express the pattern.**\n\n```html\n\u003C!-- ❌ WRONG - unnecessary ARIA -->\n\u003Cbutton role=\"button\">Click me\u003C\u002Fbutton>  \u003C!-- Button already has role -->\n\n\u003C!-- ✅ CORRECT - ARIA fills semantic gap -->\n\u003Cdiv role=\"dialog\" aria-labelledby=\"title\" aria-modal=\"true\">\n  \u003Ch2 id=\"title\">Confirm action\u003C\u002Fh2>\n  \u003C!-- No HTML dialog yet, so role needed -->\n\u003C\u002Fdiv>\n\n\u003C!-- ✅ BETTER - Use native HTML when available -->\n\u003Cdialog aria-labelledby=\"title\">\n  \u003Ch2 id=\"title\">Confirm action\u003C\u002Fh2>\n\u003C\u002Fdialog>\n```\n\n**Common ARIA patterns:**\n- `aria-label` - When visible label doesn't exist\n- `aria-labelledby` - Reference existing text as label\n- `aria-describedby` - Additional description\n- `aria-live` - Announce dynamic updates\n- `aria-expanded` - Collapsible\u002Fexpandable state\n\n**See `references\u002Faria-patterns.md` for complete patterns.**\n\n### Step 3: Implement Keyboard Navigation\n\n**All interactive elements must be keyboard-accessible:**\n\n```typescript\n\u002F\u002F Tab order management\nfunction Dialog({ onClose }) {\n  const dialogRef = useRef\u003CHTMLDivElement>(null);\n  const previousFocus = useRef\u003CHTMLElement | null>(null);\n\n  useEffect(() => {\n    \u002F\u002F Save previous focus\n    previousFocus.current = document.activeElement as HTMLElement;\n\n    \u002F\u002F 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    \u002F\u002F Trap focus within dialog\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (e.key === 'Escape') onClose();\n      if (e.key === 'Tab') {\n        \u002F\u002F Focus trap logic here\n      }\n    };\n\n    document.addEventListener('keydown', handleKeyDown);\n    return () => {\n      document.removeEventListener('keydown', handleKeyDown);\n      \u002F\u002F Restore focus on close\n      previousFocus.current?.focus();\n    };\n  }, [onClose]);\n\n  return \u003Cdiv ref={dialogRef} role=\"dialog\">...\u003C\u002Fdiv>;\n}\n```\n\n**Essential keyboard patterns:**\n- Tab\u002FShift+Tab: Navigate between focusable elements\n- Enter\u002FSpace: Activate buttons\u002Flinks\n- Arrow keys: Navigate within components (tabs, menus)\n- Escape: Close dialogs\u002Fmenus\n- Home\u002FEnd: Jump to first\u002Flast item\n\n**See `references\u002Ffocus-management.md` for complete patterns.**\n\n### Step 4: Ensure Color Contrast\n\n**WCAG AA requirements:**\n- Normal text (under 18pt): 4.5:1 contrast ratio\n- Large text (18pt+ or 14pt+ bold): 3:1 contrast ratio\n- UI components (buttons, borders): 3:1 contrast ratio\n\n```css\n\u002F* ❌ WRONG - insufficient contrast *\u002F\n:root {\n  --background: #ffffff;\n  --text: #999999;  \u002F* 2.8:1 - fails WCAG AA *\u002F\n}\n\n\u002F* ✅ CORRECT - sufficient contrast *\u002F\n:root {\n  --background: #ffffff;\n  --text: #595959;  \u002F* 4.6:1 - passes WCAG AA *\u002F\n}\n```\n\n**Testing tools:**\n- Browser DevTools (Chrome\u002FFirefox have built-in checkers)\n- Contrast checker extensions\n- axe DevTools extension\n\n**See `references\u002Fcolor-contrast.md` for complete guide.**\n\n### Step 5: Make Forms Accessible\n\n**Every form input needs a visible label:**\n\n```html\n\u003C!-- ❌ WRONG - placeholder is not a label -->\n\u003Cinput type=\"email\" placeholder=\"Email address\">\n\n\u003C!-- ✅ CORRECT - proper label -->\n\u003Clabel for=\"email\">Email address\u003C\u002Flabel>\n\u003Cinput type=\"email\" id=\"email\" name=\"email\" required aria-required=\"true\">\n```\n\n**Error handling:**\n\n```html\n\u003Clabel for=\"email\">Email address\u003C\u002Flabel>\n\u003Cinput\n  type=\"email\"\n  id=\"email\"\n  name=\"email\"\n  aria-invalid=\"true\"\n  aria-describedby=\"email-error\"\n>\n\u003Cspan id=\"email-error\" role=\"alert\">\n  Please enter a valid email address\n\u003C\u002Fspan>\n```\n\n**Live regions for dynamic errors:**\n\n```html\n\u003Cdiv role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n  Form submission failed. Please fix the errors above.\n\u003C\u002Fdiv>\n```\n\n**See `references\u002Fforms-validation.md` for complete patterns.**\n\n---\n\n## Critical Rules\n\n### Always Do\n\n✅ 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\u002FUI\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\n\n### Never Do\n\n❌ 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)\n\n---\n\n## Known Issues Prevention\n\nThis skill prevents **12** documented accessibility issues:\n\n### Issue #1: Missing Focus Indicators\n\n**Error**: Interactive elements have no visible focus indicator\n**Source**: WCAG 2.4.7 (Focus Visible)\n**Why It Happens**: CSS reset removes default outline\n**Prevention**: Always provide custom focus-visible styles\n\n### Issue #2: Insufficient Color Contrast\n\n**Error**: Text has less than 4.5:1 contrast ratio\n**Source**: WCAG 1.4.3 (Contrast Minimum)\n**Why It Happens**: Using light gray text on white background\n**Prevention**: Test all text colors with contrast checker\n\n### Issue #3: Missing Alt Text\n\n**Error**: Images missing alt attributes\n**Source**: WCAG 1.1.1 (Non-text Content)\n**Why It Happens**: Forgot to add or thought it was optional\n**Prevention**: Add alt=\"\" for decorative, descriptive alt for meaningful images\n\n### Issue #4: Keyboard Navigation Broken\n\n**Error**: Interactive elements not reachable by keyboard\n**Source**: WCAG 2.1.1 (Keyboard)\n**Why It Happens**: Using div onClick instead of button\n**Prevention**: Use semantic interactive elements (button, a)\n\n### Issue #5: Form Inputs Without Labels\n\n**Error**: Input fields missing associated labels\n**Source**: WCAG 3.3.2 (Labels or Instructions)\n**Why It Happens**: Using placeholder as label\n**Prevention**: Always use `\u003Clabel>` element with for\u002Fid association\n\n### Issue #6: Skipped Heading Levels\n\n**Error**: Heading hierarchy jumps from h1 to h3\n**Source**: WCAG 1.3.1 (Info and Relationships)\n**Why It Happens**: Using headings for visual styling instead of semantics\n**Prevention**: Use headings in order, style with CSS\n\n### Issue #7: No Focus Trap in Dialogs\n\n**Error**: Tab key exits dialog to background content\n**Source**: WCAG 2.4.3 (Focus Order)\n**Why It Happens**: No focus trap implementation\n**Prevention**: Implement focus trap for modal dialogs\n\n### Issue #8: Missing aria-live for Dynamic Content\n\n**Error**: Screen reader doesn't announce updates\n**Source**: WCAG 4.1.3 (Status Messages)\n**Why It Happens**: Dynamic content added without announcement\n**Prevention**: Use aria-live=\"polite\" or \"assertive\"\n\n### Issue #9: Color-Only Information\n\n**Error**: Using only color to convey status\n**Source**: WCAG 1.4.1 (Use of Color)\n**Why It Happens**: Red text for errors without icon\u002Ftext\n**Prevention**: Add icon + text label, not just color\n\n### Issue #10: Non-descriptive Link Text\n\n**Error**: Links with \"click here\" or \"read more\"\n**Source**: WCAG 2.4.4 (Link Purpose)\n**Why It Happens**: Generic link text without context\n**Prevention**: Use descriptive link text or aria-label\n\n### Issue #11: Auto-playing Media\n\n**Error**: Video\u002Faudio auto-plays without user control\n**Source**: WCAG 1.4.2 (Audio Control)\n**Why It Happens**: Autoplay attribute without controls\n**Prevention**: Require user interaction to start media\n\n### Issue #12: Inaccessible Custom Controls\n\n**Error**: Custom select\u002Fcheckbox without keyboard support\n**Source**: WCAG 4.1.2 (Name, Role, Value)\n**Why It Happens**: Building from divs without ARIA\n**Prevention**: Use native elements or implement full ARIA pattern\n\n---\n\n## WCAG 2.1 AA Quick Checklist\n\n### Perceivable\n\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\n\n### Operable\n\n- [ ] All functionality keyboard accessible\n- [ ] No keyboard traps\n- [ ] Visible focus indicators\n- [ ] Users can pause\u002Fstop\u002Fhide 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\n\n### Understandable\n\n- [ ] Page language specified (`\u003Chtml lang=\"en\">`)\n- [ ] Language changes marked (`\u003Cspan lang=\"es\">`)\n- [ ] No unexpected context changes on focus\u002Finput\n- [ ] Consistent navigation across site\n- [ ] Form labels\u002Finstructions provided\n- [ ] Input errors identified and described\n- [ ] Error prevention for legal\u002Ffinancial\u002Fdata changes\n\n### Robust\n\n- [ ] Valid HTML (no parsing errors)\n- [ ] Name, role, value available for all UI components\n- [ ] Status messages identified (aria-live)\n\n---\n\n## Testing Workflow\n\n### 1. Keyboard-Only Testing (5 minutes)\n\n```\n1. Unplug mouse or hide cursor\n2. Tab through entire page\n   - Can you reach all interactive elements?\n   - Can you activate all buttons\u002Flinks?\n   - Is focus order logical?\n3. Use Enter\u002FSpace to activate\n4. Use Escape to close dialogs\n5. Use arrow keys in menus\u002Ftabs\n```\n\n### 2. Screen Reader Testing (10 minutes)\n\n**NVDA (Windows - Free)**:\n- Download: https:\u002F\u002Fwww.nvaccess.org\u002Fdownload\u002F\n- Start: Ctrl+Alt+N\n- Navigate: Arrow keys or Tab\n- Read: NVDA+Down arrow\n- Stop: NVDA+Q\n\n**VoiceOver (Mac - Built-in)**:\n- Start: Cmd+F5\n- Navigate: VO+Right\u002FLeft arrow (VO = Ctrl+Option)\n- Read: VO+A (read all)\n- Stop: Cmd+F5\n\n**What to test:**\n- Are all interactive elements announced?\n- Are images described properly?\n- Are form labels read with inputs?\n- Are dynamic updates announced?\n- Is heading structure clear?\n\n### 3. Automated Testing\n\n**axe DevTools** (Browser extension - highly recommended):\n- Install: Chrome\u002FFirefox extension\n- Run: F12 → axe DevTools tab → Scan\n- Fix: Review violations, follow remediation\n- Retest: Scan again after fixes\n\n**Lighthouse** (Built into Chrome):\n- Open DevTools (F12)\n- Lighthouse tab\n- Select \"Accessibility\" category\n- Generate report\n- Score 90+ is good, 100 is ideal\n\n---\n\n## Common Patterns\n\n### Pattern 1: Accessible Dialog\u002FModal\n\n```typescript\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\u003CHTMLDivElement>(null);\n\n  useEffect(() => {\n    if (!isOpen) return;\n\n    const previousFocus = document.activeElement as HTMLElement;\n\n    \u002F\u002F 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    \u002F\u002F 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    \u003C>\n      {\u002F* Backdrop *\u002F}\n      \u003Cdiv\n        className=\"dialog-backdrop\"\n        onClick={onClose}\n        aria-hidden=\"true\"\n      \u002F>\n\n      {\u002F* Dialog *\u002F}\n      \u003Cdiv\n        ref={dialogRef}\n        role=\"dialog\"\n        aria-modal=\"true\"\n        aria-labelledby=\"dialog-title\"\n        className=\"dialog\"\n      >\n        \u003Ch2 id=\"dialog-title\">{title}\u003C\u002Fh2>\n        \u003Cdiv className=\"dialog-content\">{children}\u003C\u002Fdiv>\n        \u003Cbutton onClick={onClose} aria-label=\"Close dialog\">×\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n    \u003C\u002F>\n  );\n}\n```\n\n**When to use**: Any modal dialog or overlay that blocks interaction with background content.\n\n### Pattern 2: Accessible Tabs\n\n```typescript\nfunction Tabs({ tabs }: { tabs: Array\u003C{ 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    \u003Cdiv>\n      \u003Cdiv role=\"tablist\" aria-label=\"Content tabs\">\n        {tabs.map((tab, index) => (\n          \u003Cbutton\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          \u003C\u002Fbutton>\n        ))}\n      \u003C\u002Fdiv>\n      {tabs.map((tab, index) => (\n        \u003Cdiv\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        \u003C\u002Fdiv>\n      ))}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n**When to use**: Tabbed interface with multiple panels.\n\n### Pattern 3: Skip Links\n\n```html\n\u003C!-- Place at very top of body -->\n\u003Ca href=\"#main-content\" class=\"skip-link\">\n  Skip to main content\n\u003C\u002Fa>\n\n\u003Cstyle>\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\u003C\u002Fstyle>\n\n\u003C!-- Then in your layout -->\n\u003Cmain id=\"main-content\" tabindex=\"-1\">\n  \u003C!-- Page content -->\n\u003C\u002Fmain>\n```\n\n**When to use**: All multi-page websites with navigation\u002Fheader before main content.\n\n### Pattern 4: Accessible Form with Validation\n\n```typescript\nfunction ContactForm() {\n  const [errors, setErrors] = useState\u003CRecord\u003Cstring, string>>({});\n  const [touched, setTouched] = useState\u003CRecord\u003Cstring, boolean>>({});\n\n  const validateEmail = (email: string) => {\n    if (!email) return 'Email is required';\n    if (!\u002F^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$\u002F.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    \u003Cform>\n      \u003Cdiv>\n        \u003Clabel htmlFor=\"email\">Email address *\u003C\u002Flabel>\n        \u003Cinput\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        \u002F>\n        {touched.email && errors.email && (\n          \u003Cspan id=\"email-error\" role=\"alert\" className=\"error\">\n            {errors.email}\n          \u003C\u002Fspan>\n        )}\n      \u003C\u002Fdiv>\n\n      \u003Cbutton type=\"submit\">Submit\u003C\u002Fbutton>\n\n      {\u002F* Global form error *\u002F}\n      \u003Cdiv role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n        {\u002F* Dynamic error message appears here *\u002F}\n      \u003C\u002Fdiv>\n    \u003C\u002Fform>\n  );\n}\n```\n\n**When to use**: All forms with validation.\n\n---\n\n## Using Bundled Resources\n\n### References (references\u002F)\n\nDetailed documentation for deep dives:\n\n- **wcag-checklist.md** - Complete WCAG 2.1 Level A & AA requirements with examples\n- **semantic-html.md** - Element selection guide, when to use which tag\n- **aria-patterns.md** - ARIA roles, states, properties, and when to use them\n- **focus-management.md** - Focus order, focus traps, focus restoration patterns\n- **color-contrast.md** - Contrast requirements, testing tools, color palette tips\n- **forms-validation.md** - Accessible form patterns, error handling, announcements\n\n**When Claude should load these**:\n- User asks for complete WCAG checklist\n- Deep dive into specific pattern (tabs, accordions, etc.)\n- Color contrast issues or palette design\n- Complex form validation scenarios\n\n### Agents (agents\u002F)\n\n- **a11y-auditor.md** - Automated accessibility auditor that checks pages for violations\n\n**When to use**: Request accessibility audit of existing page\u002Fcomponent.\n\n---\n\n## Advanced Topics\n\n### ARIA Live Regions\n\nThree politeness levels:\n\n```html\n\u003C!-- Polite: Wait for screen reader to finish current announcement -->\n\u003Cdiv aria-live=\"polite\">New messages: 3\u003C\u002Fdiv>\n\n\u003C!-- Assertive: Interrupt immediately -->\n\u003Cdiv aria-live=\"assertive\" role=\"alert\">\n  Error: Form submission failed\n\u003C\u002Fdiv>\n\n\u003C!-- Off: Don't announce (default) -->\n\u003Cdiv aria-live=\"off\">Loading...\u003C\u002Fdiv>\n```\n\n**Best practices:**\n- Use `polite` for non-critical updates (notifications, counters)\n- Use `assertive` for errors and critical alerts\n- Use `aria-atomic=\"true\"` to read entire region on change\n- Keep messages concise and meaningful\n\n### Focus Management in SPAs\n\nReact Router doesn't reset focus on navigation - you need to handle it:\n\n```typescript\nfunction App() {\n  const location = useLocation();\n  const mainRef = useRef\u003CHTMLElement>(null);\n\n  useEffect(() => {\n    \u002F\u002F Focus main content on route change\n    mainRef.current?.focus();\n    \u002F\u002F 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 \u003Cmain ref={mainRef} tabIndex={-1} id=\"main-content\">...\u003C\u002Fmain>;\n}\n```\n\n### Accessible Data Tables\n\n```html\n\u003Ctable>\n  \u003Ccaption>Monthly sales by region\u003C\u002Fcaption>\n  \u003Cthead>\n    \u003Ctr>\n      \u003Cth scope=\"col\">Region\u003C\u002Fth>\n      \u003Cth scope=\"col\">Q1\u003C\u002Fth>\n      \u003Cth scope=\"col\">Q2\u003C\u002Fth>\n    \u003C\u002Ftr>\n  \u003C\u002Fthead>\n  \u003Ctbody>\n    \u003Ctr>\n      \u003Cth scope=\"row\">North\u003C\u002Fth>\n      \u003Ctd>$10,000\u003C\u002Ftd>\n      \u003Ctd>$12,000\u003C\u002Ftd>\n    \u003C\u002Ftr>\n  \u003C\u002Ftbody>\n\u003C\u002Ftable>\n```\n\n**Key attributes:**\n- `\u003Ccaption>` - Describes table purpose\n- `scope=\"col\"` - Identifies column headers\n- `scope=\"row\"` - Identifies row headers\n- Associates data cells with headers for screen readers\n\n---\n\n## Official Documentation\n\n- **WCAG 2.1**: https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002Fquickref\u002F\n- **MDN Accessibility**: https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAccessibility\n- **ARIA Authoring Practices**: https:\u002F\u002Fwww.w3.org\u002FWAI\u002FARIA\u002Fapg\u002F\n- **WebAIM**: https:\u002F\u002Fwebaim.org\u002Farticles\u002F\n- **axe DevTools**: https:\u002F\u002Fwww.deque.com\u002Faxe\u002Fdevtools\u002F\n\n---\n\n## Troubleshooting\n\n### Problem: Focus indicators not visible\n\n**Symptoms**: Can tab through page but don't see where focus is\n**Cause**: CSS removed outlines or insufficient contrast\n**Solution**:\n```css\n*:focus-visible {\n  outline: 2px solid var(--primary);\n  outline-offset: 2px;\n}\n```\n\n### Problem: Screen reader not announcing updates\n\n**Symptoms**: Dynamic content changes but no announcement\n**Cause**: No aria-live region\n**Solution**: Wrap dynamic content in `\u003Cdiv aria-live=\"polite\">` or use role=\"alert\"\n\n### Problem: Dialog focus escapes to background\n\n**Symptoms**: Tab key navigates to elements behind dialog\n**Cause**: No focus trap\n**Solution**: Implement focus trap (see Pattern 1 above)\n\n### Problem: Form errors not announced\n\n**Symptoms**: Visual errors appear but screen reader doesn't notice\n**Cause**: No aria-invalid or role=\"alert\"\n**Solution**: Use aria-invalid + aria-describedby pointing to error message with role=\"alert\"\n\n---\n\n## Complete Setup Checklist\n\nUse this for every page\u002Fcomponent:\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 `\u003Chtml 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\n---\n\n**Questions? Issues?**\n\n1. Check `references\u002Fwcag-checklist.md` for complete requirements\n2. Use `\u002Fa11y-auditor` agent to scan your page\n3. Run axe DevTools for automated testing\n4. Test with actual keyboard + screen reader\n\n---\n\n**Standards**: WCAG 2.1 Level AA\n**Testing Tools**: axe DevTools, Lighthouse, NVDA, VoiceOver\n**Success Criteria**: 90+ Lighthouse score, 0 critical violations\n","# Web Accessibility (WCAG 2.1 AA)\n\n**Status**: Production Ready ✅\n**Last Updated**: 2026-01-14\n**Dependencies**: None (framework-agnostic)\n**Standards**: WCAG 2.1 Level AA\n\n---\n\n## Quick Start (5 Minutes)\n\n### 1. Semantic HTML Foundation\n\nChoose the right element - don't use `div` for everything:\n\n```html\n\u003C!-- ❌ WRONG - divs with onClick -->\n\u003Cdiv onclick=\"submit()\">Submit\u003C\u002Fdiv>\n\u003Cdiv onclick=\"navigate()\">Next page\u003C\u002Fdiv>\n\n\u003C!-- ✅ CORRECT - semantic elements -->\n\u003Cbutton type=\"submit\">Submit\u003C\u002Fbutton>\n\u003Ca href=\"\u002Fnext\">Next page\u003C\u002Fa>\n```\n\n**Why this matters:**\n- Semantic elements have built-in keyboard support\n- Screen readers announce role automatically\n- Browser provides default accessible behaviors\n\n### 2. Focus Management\n\nMake interactive elements keyboard-accessible:\n\n```css\n\u002F* ❌ WRONG - removes focus outline *\u002F\nbutton:focus { outline: none; }\n\n\u002F* ✅ CORRECT - custom accessible outline *\u002F\nbutton:focus-visible {\n  outline: 2px solid var(--primary);\n  outline-offset: 2px;\n}\n```\n\n**CRITICAL:**\n- Never remove focus outlines without replacement\n- Use `:focus-visible` to show only on keyboard focus\n- Ensure 3:1 contrast ratio for focus indicators\n\n### 3. Text Alternatives\n\nEvery non-text element needs a text alternative:\n\n```html\n\u003C!-- ❌ WRONG - no alt text -->\n\u003Cimg src=\"logo.png\">\n\u003Cbutton>\u003Csvg>...\u003C\u002Fsvg>\u003C\u002Fbutton>\n\n\u003C!-- ✅ CORRECT - proper alternatives -->\n\u003Cimg src=\"logo.png\" alt=\"Company Name\">\n\u003Cbutton aria-label=\"Close dialog\">\u003Csvg>...\u003C\u002Fsvg>\u003C\u002Fbutton>\n```\n\n---\n\n## The 5-Step Accessibility Process\n\n### Step 1: Choose Semantic HTML\n\n**Decision tree for element selection:**\n\n```\nNeed clickable element?\n├─ Navigates to another page? → \u003Ca href=\"...\">\n├─ Submits form? → \u003Cbutton type=\"submit\">\n├─ Opens dialog? → \u003Cbutton aria-haspopup=\"dialog\">\n└─ Other action? → \u003Cbutton type=\"button\">\n\nGrouping content?\n├─ Self-contained article? → \u003Carticle>\n├─ Thematic section? → \u003Csection>\n├─ Navigation links? → \u003Cnav>\n└─ Supplementary info? → \u003Caside>\n\nForm element?\n├─ Text input? → \u003Cinput type=\"text\">\n├─ Multiple choice? → \u003Cselect> or \u003Cinput type=\"radio\">\n├─ Toggle? → \u003Cinput type=\"checkbox\"> or \u003Cbutton aria-pressed>\n└─ Long text? → \u003Ctextarea>\n```\n\n**See `references\u002Fsemantic-html.md` for complete guide.**\n\n### Step 2: Add ARIA When Needed\n\n**Golden rule: Use ARIA only when HTML can't express the pattern.**\n\n```html\n\u003C!-- ❌ WRONG - unnecessary ARIA -->\n\u003Cbutton role=\"button\">Click me\u003C\u002Fbutton>  \u003C!-- Button already has role -->\n\n\u003C!-- ✅ CORRECT - ARIA fills semantic gap -->\n\u003Cdiv role=\"dialog\" aria-labelledby=\"title\" aria-modal=\"true\">\n  \u003Ch2 id=\"title\">Confirm action\u003C\u002Fh2>\n  \u003C!-- No HTML dialog yet, so role needed -->\n\u003C\u002Fdiv>\n\n\u003C!-- ✅ BETTER - Use native HTML when available -->\n\u003Cdialog aria-labelledby=\"title\">\n  \u003Ch2 id=\"title\">Confirm action\u003C\u002Fh2>\n\u003C\u002Fdialog>\n```\n\n**Common ARIA patterns:**\n- `aria-label` - When visible label doesn't exist\n- `aria-labelledby` - Reference existing text as label\n- `aria-describedby` - Additional description\n- `aria-live` - Announce dynamic updates\n- `aria-expanded` - Collapsible\u002Fexpandable state\n\n**See `references\u002Faria-patterns.md` for complete patterns.**\n\n### Step 3: Implement Keyboard Navigation\n\n**All interactive elements must be keyboard-accessible:**\n\n```typescript\n\u002F\u002F Tab order management\nfunction Dialog({ onClose }) {\n  const dialogRef = useRef\u003CHTMLDivElement>(null);\n  const previousFocus = useRef\u003CHTMLElement | null>(null);\n\n  useEffect(() => {\n    \u002F\u002F Save previous focus\n    previousFocus.current = document.activeElement as HTMLElement;\n\n    \u002F\u002F 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    \u002F\u002F Trap focus within dialog\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (e.key === 'Escape') onClose();\n      if (e.key === 'Tab') {\n        \u002F\u002F Focus trap logic here\n      }\n    };\n\n    document.addEventListener('keydown', handleKeyDown);\n    return () => {\n      document.removeEventListener('keydown', handleKeyDown);\n      \u002F\u002F Restore focus on close\n      previousFocus.current?.focus();\n    };\n  }, [onClose]);\n\n  return \u003Cdiv ref={dialogRef} role=\"dialog\">...\u003C\u002Fdiv>;\n}\n```\n\n**Essential keyboard patterns:**\n- Tab\u002FShift+Tab: Navigate between focusable elements\n- Enter\u002FSpace: Activate buttons\u002Flinks\n- Arrow keys: Navigate within components (tabs, menus)\n- Escape: Close dialogs\u002Fmenus\n- Home\u002FEnd: Jump to first\u002Flast item\n\n**See `references\u002Ffocus-management.md` for complete patterns.**\n\n### Step 4: Ensure Color Contrast\n\n**WCAG AA requirements:**\n- Normal text (under 18pt): 4.5:1 contrast ratio\n- Large text (18pt+ or 14pt+ bold): 3:1 contrast ratio\n- UI components (buttons, borders): 3:1 contrast ratio\n\n```css\n\u002F* ❌ WRONG - insufficient contrast *\u002F\n:root {\n  --background: #ffffff;\n  --text: #999999;  \u002F* 2.8:1 - fails WCAG AA *\u002F\n}\n\n\u002F* ✅ CORRECT - sufficient contrast *\u002F\n:root {\n  --background: #ffffff;\n  --text: #595959;  \u002F* 4.6:1 - passes WCAG AA *\u002F\n}\n```\n\n**Testing tools:**\n- Browser DevTools (Chrome\u002FFirefox have built-in checkers)\n- Contrast checker extensions\n- axe DevTools extension\n\n**See `references\u002Fcolor-contrast.md` for complete guide.**\n\n### Step 5: Make Forms Accessible\n\n**Every form input needs a visible label:**\n\n```html\n\u003C!-- ❌ WRONG - placeholder is not a label -->\n\u003Cinput type=\"email\" placeholder=\"Email address\">\n\n\u003C!-- ✅ CORRECT - proper label -->\n\u003Clabel for=\"email\">Email address\u003C\u002Flabel>\n\u003Cinput type=\"email\" id=\"email\" name=\"email\" required aria-required=\"true\">\n```\n\n**Error handling:**\n\n```html\n\u003Clabel for=\"email\">Email address\u003C\u002Flabel>\n\u003Cinput\n  type=\"email\"\n  id=\"email\"\n  name=\"email\"\n  aria-invalid=\"true\"\n  aria-describedby=\"email-error\"\n>\n\u003Cspan id=\"email-error\" role=\"alert\">\n  Please enter a valid email address\n\u003C\u002Fspan>\n```\n\n**Live regions for dynamic errors:**\n\n```html\n\u003Cdiv role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n  Form submission failed. Please fix the errors above.\n\u003C\u002Fdiv>\n```\n\n**See `references\u002Fforms-validation.md` for complete patterns.**\n\n---\n\n## Critical Rules\n\n### Always Do\n\n✅ 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\u002FUI\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\n\n### Never Do\n\n❌ 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)\n\n---\n\n## Known Issues Prevention\n\nThis skill prevents **12** documented accessibility issues:\n\n### Issue #1: Missing Focus Indicators\n\n**Error**: Interactive elements have no visible focus indicator\n**Source**: WCAG 2.4.7 (Focus Visible)\n**Why It Happens**: CSS reset removes default outline\n**Prevention**: Always provide custom focus-visible styles\n\n### Issue #2: Insufficient Color Contrast\n\n**Error**: Text has less than 4.5:1 contrast ratio\n**Source**: WCAG 1.4.3 (Contrast Minimum)\n**Why It Happens**: Using light gray text on white background\n**Prevention**: Test all text colors with contrast checker\n\n### Issue #3: Missing Alt Text\n\n**Error**: Images missing alt attributes\n**Source**: WCAG 1.1.1 (Non-text Content)\n**Why It Happens**: Forgot to add or thought it was optional\n**Prevention**: Add alt=\"\" for decorative, descriptive alt for meaningful images\n\n### Issue #4: Keyboard Navigation Broken\n\n**Error**: Interactive elements not reachable by keyboard\n**Source**: WCAG 2.1.1 (Keyboard)\n**Why It Happens**: Using div onClick instead of button\n**Prevention**: Use semantic interactive elements (button, a)\n\n### Issue #5: Form Inputs Without Labels\n\n**Error**: Input fields missing associated labels\n**Source**: WCAG 3.3.2 (Labels or Instructions)\n**Why It Happens**: Using placeholder as label\n**Prevention**: Always use `\u003Clabel>` element with for\u002Fid association\n\n### Issue #6: Skipped Heading Levels\n\n**Error**: Heading hierarchy jumps from h1 to h3\n**Source**: WCAG 1.3.1 (Info and Relationships)\n**Why It Happens**: Using headings for visual styling instead of semantics\n**Prevention**: Use headings in order, style with CSS\n\n### Issue #7: No Focus Trap in Dialogs\n\n**Error**: Tab key exits dialog to background content\n**Source**: WCAG 2.4.3 (Focus Order)\n**Why It Happens**: No focus trap implementation\n**Prevention**: Implement focus trap for modal dialogs\n\n### Issue #8: Missing aria-live for Dynamic Content\n\n**Error**: Screen reader doesn't announce updates\n**Source**: WCAG 4.1.3 (Status Messages)\n**Why It Happens**: Dynamic content added without announcement\n**Prevention**: Use aria-live=\"polite\" or \"assertive\"\n\n### Issue #9: Color-Only Information\n\n**Error**: Using only color to convey status\n**Source**: WCAG 1.4.1 (Use of Color)\n**Why It Happens**: Red text for errors without icon\u002Ftext\n**Prevention**: Add icon + text label, not just color\n\n### Issue #10: Non-descriptive Link Text\n\n**Error**: Links with \"click here\" or \"read more\"\n**Source**: WCAG 2.4.4 (Link Purpose)\n**Why It Happens**: Generic link text without context\n**Prevention**: Use descriptive link text or aria-label\n\n### Issue #11: Auto-playing Media\n\n**Error**: Video\u002Faudio auto-plays without user control\n**Source**: WCAG 1.4.2 (Audio Control)\n**Why It Happens**: Autoplay attribute without controls\n**Prevention**: Require user interaction to start media\n\n### Issue #12: Inaccessible Custom Controls\n\n**Error**: Custom select\u002Fcheckbox without keyboard support\n**Source**: WCAG 4.1.2 (Name, Role, Value)\n**Why It Happens**: Building from divs without ARIA\n**Prevention**: Use native elements or implement full ARIA pattern\n\n---\n\n## WCAG 2.1 AA Quick Checklist\n\n### Perceivable\n\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\n\n### Operable\n\n- [ ] All functionality keyboard accessible\n- [ ] No keyboard traps\n- [ ] Visible focus indicators\n- [ ] Users can pause\u002Fstop\u002Fhide 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\n\n### Understandable\n\n- [ ] Page language specified (`\u003Chtml lang=\"en\">`)\n- [ ] Language changes marked (`\u003Cspan lang=\"es\">`)\n- [ ] No unexpected context changes on focus\u002Finput\n- [ ] Consistent navigation across site\n- [ ] Form labels\u002Finstructions provided\n- [ ] Input errors identified and described\n- [ ] Error prevention for legal\u002Ffinancial\u002Fdata changes\n\n### Robust\n\n- [ ] Valid HTML (no parsing errors)\n- [ ] Name, role, value available for all UI components\n- [ ] Status messages identified (aria-live)\n\n---\n\n## Testing Workflow\n\n### 1. Keyboard-Only Testing (5 minutes)\n\n```\n1. Unplug mouse or hide cursor\n2. Tab through entire page\n   - Can you reach all interactive elements?\n   - Can you activate all buttons\u002Flinks?\n   - Is focus order logical?\n3. Use Enter\u002FSpace to activate\n4. Use Escape to close dialogs\n5. Use arrow keys in menus\u002Ftabs\n```\n\n### 2. Screen Reader Testing (10 minutes)\n\n**NVDA (Windows - Free)**:\n- Download: https:\u002F\u002Fwww.nvaccess.org\u002Fdownload\u002F\n- Start: Ctrl+Alt+N\n- Navigate: Arrow keys or Tab\n- Read: NVDA+Down arrow\n- Stop: NVDA+Q\n\n**VoiceOver (Mac - Built-in)**:\n- Start: Cmd+F5\n- Navigate: VO+Right\u002FLeft arrow (VO = Ctrl+Option)\n- Read: VO+A (read all)\n- Stop: Cmd+F5\n\n**What to test:**\n- Are all interactive elements announced?\n- Are images described properly?\n- Are form labels read with inputs?\n- Are dynamic updates announced?\n- Is heading structure clear?\n\n### 3. Automated Testing\n\n**axe DevTools** (Browser extension - highly recommended):\n- Install: Chrome\u002FFirefox extension\n- Run: F12 → axe DevTools tab → Scan\n- Fix: Review violations, follow remediation\n- Retest: Scan again after fixes\n\n**Lighthouse** (Built into Chrome):\n- Open DevTools (F12)\n- Lighthouse tab\n- Select \"Accessibility\" category\n- Generate report\n- Score 90+ is good, 100 is ideal\n\n---\n\n## Common Patterns\n\n### Pattern 1: Accessible Dialog\u002FModal\n\n```typescript\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\u003CHTMLDivElement>(null);\n\n  useEffect(() => {\n    if (!isOpen) return;\n\n    const previousFocus = document.activeElement as HTMLElement;\n\n    \u002F\u002F 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    \u002F\u002F 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    \u003C>\n      {\u002F* Backdrop *\u002F}\n      \u003Cdiv\n        className=\"dialog-backdrop\"\n        onClick={onClose}\n        aria-hidden=\"true\"\n      \u002F>\n\n      {\u002F* Dialog *\u002F}\n      \u003Cdiv\n        ref={dialogRef}\n        role=\"dialog\"\n        aria-modal=\"true\"\n        aria-labelledby=\"dialog-title\"\n        className=\"dialog\"\n      >\n        \u003Ch2 id=\"dialog-title\">{title}\u003C\u002Fh2>\n        \u003Cdiv className=\"dialog-content\">{children}\u003C\u002Fdiv>\n        \u003Cbutton onClick={onClose} aria-label=\"Close dialog\">×\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n    \u003C\u002F>\n  );\n}\n```\n\n**When to use**: Any modal dialog or overlay that blocks interaction with background content.\n\n### Pattern 2: Accessible Tabs\n\n```typescript\nfunction Tabs({ tabs }: { tabs: Array\u003C{ 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    \u003Cdiv>\n      \u003Cdiv role=\"tablist\" aria-label=\"Content tabs\">\n        {tabs.map((tab, index) => (\n          \u003Cbutton\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          \u003C\u002Fbutton>\n        ))}\n      \u003C\u002Fdiv>\n      {tabs.map((tab, index) => (\n        \u003Cdiv\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        \u003C\u002Fdiv>\n      ))}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n**When to use**: Tabbed interface with multiple panels.\n\n### Pattern 3: Skip Links\n\n```html\n\u003C!-- Place at very top of body -->\n\u003Ca href=\"#main-content\" class=\"skip-link\">\n  Skip to main content\n\u003C\u002Fa>\n\n\u003Cstyle>\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\u003C\u002Fstyle>\n\n\u003C!-- Then in your layout -->\n\u003Cmain id=\"main-content\" tabindex=\"-1\">\n  \u003C!-- Page content -->\n\u003C\u002Fmain>\n```\n\n**When to use**: All multi-page websites with navigation\u002Fheader before main content.\n\n### Pattern 4: Accessible Form with Validation\n\n```typescript\nfunction ContactForm() {\n  const [errors, setErrors] = useState\u003CRecord\u003Cstring, string>>({});\n  const [touched, setTouched] = useState\u003CRecord\u003Cstring, boolean>>({});\n\n  const validateEmail = (email: string) => {\n    if (!email) return 'Email is required';\n    if (!\u002F^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$\u002F.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    \u003Cform>\n      \u003Cdiv>\n        \u003Clabel htmlFor=\"email\">Email address *\u003C\u002Flabel>\n        \u003Cinput\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        \u002F>\n        {touched.email && errors.email && (\n          \u003Cspan id=\"email-error\" role=\"alert\" className=\"error\">\n            {errors.email}\n          \u003C\u002Fspan>\n        )}\n      \u003C\u002Fdiv>\n\n      \u003Cbutton type=\"submit\">Submit\u003C\u002Fbutton>\n\n      {\u002F* Global form error *\u002F}\n      \u003Cdiv role=\"alert\" aria-live=\"assertive\" aria-atomic=\"true\">\n        {\u002F* Dynamic error message appears here *\u002F}\n      \u003C\u002Fdiv>\n    \u003C\u002Fform>\n  );\n}\n```\n\n**When to use**: All forms with validation.\n\n---\n\n## Using Bundled Resources\n\n### References (references\u002F)\n\nDetailed documentation for deep dives:\n\n- **wcag-checklist.md** - Complete WCAG 2.1 Level A & AA requirements with examples\n- **semantic-html.md** - Element selection guide, when to use which tag\n- **aria-patterns.md** - ARIA roles, states, properties, and when to use them\n- **focus-management.md** - Focus order, focus traps, focus restoration patterns\n- **color-contrast.md** - Contrast requirements, testing tools, color palette tips\n- **forms-validation.md** - Accessible form patterns, error handling, announcements\n\n**When Claude should load these**:\n- User asks for complete WCAG checklist\n- Deep dive into specific pattern (tabs, accordions, etc.)\n- Color contrast issues or palette design\n- Complex form validation scenarios\n\n### Agents (agents\u002F)\n\n- **a11y-auditor.md** - Automated accessibility auditor that checks pages for violations\n\n**When to use**: Request accessibility audit of existing page\u002Fcomponent.\n\n---\n\n## Advanced Topics\n\n### ARIA Live Regions\n\nThree politeness levels:\n\n```html\n\u003C!-- Polite: Wait for screen reader to finish current announcement -->\n\u003Cdiv aria-live=\"polite\">New messages: 3\u003C\u002Fdiv>\n\n\u003C!-- Assertive: Interrupt immediately -->\n\u003Cdiv aria-live=\"assertive\" role=\"alert\">\n  Error: Form submission failed\n\u003C\u002Fdiv>\n\n\u003C!-- Off: Don't announce (default) -->\n\u003Cdiv aria-live=\"off\">Loading...\u003C\u002Fdiv>\n```\n\n**Best practices:**\n- Use `polite` for non-critical updates (notifications, counters)\n- Use `assertive` for errors and critical alerts\n- Use `aria-atomic=\"true\"` to read entire region on change\n- Keep messages concise and meaningful\n\n### Focus Management in SPAs\n\nReact Router doesn't reset focus on navigation - you need to handle it:\n\n```typescript\nfunction App() {\n  const location = useLocation();\n  const mainRef = useRef\u003CHTMLElement>(null);\n\n  useEffect(() => {\n    \u002F\u002F Focus main content on route change\n    mainRef.current?.focus();\n    \u002F\u002F 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 \u003Cmain ref={mainRef} tabIndex={-1} id=\"main-content\">...\u003C\u002Fmain>;\n}\n```\n\n### Accessible Data Tables\n\n```html\n\u003Ctable>\n  \u003Ccaption>Monthly sales by region\u003C\u002Fcaption>\n  \u003Cthead>\n    \u003Ctr>\n      \u003Cth scope=\"col\">Region\u003C\u002Fth>\n      \u003Cth scope=\"col\">Q1\u003C\u002Fth>\n      \u003Cth scope=\"col\">Q2\u003C\u002Fth>\n    \u003C\u002Ftr>\n  \u003C\u002Fthead>\n  \u003Ctbody>\n    \u003Ctr>\n      \u003Cth scope=\"row\">North\u003C\u002Fth>\n      \u003Ctd>$10,000\u003C\u002Ftd>\n      \u003Ctd>$12,000\u003C\u002Ftd>\n    \u003C\u002Ftr>\n  \u003C\u002Ftbody>\n\u003C\u002Ftable>\n```\n\n**Key attributes:**\n- `\u003Ccaption>` - Describes table purpose\n- `scope=\"col\"` - Identifies column headers\n- `scope=\"row\"` - Identifies row headers\n- Associates data cells with headers for screen readers\n\n---\n\n## Official Documentation\n\n- **WCAG 2.1**: https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002Fquickref\u002F\n- **MDN Accessibility**: https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAccessibility\n- **ARIA Authoring Practices**: https:\u002F\u002Fwww.w3.org\u002FWAI\u002FARIA\u002Fapg\u002F\n- **WebAIM**: https:\u002F\u002Fwebaim.org\u002Farticles\u002F\n- **axe DevTools**: https:\u002F\u002Fwww.deque.com\u002Faxe\u002Fdevtools\u002F\n\n---\n\n## Troubleshooting\n\n### Problem: Focus indicators not visible\n\n**Symptoms**: Can tab through page but don't see where focus is\n**Cause**: CSS removed outlines or insufficient contrast\n**Solution**:\n```css\n*:focus-visible {\n  outline: 2px solid var(--primary);\n  outline-offset: 2px;\n}\n```\n\n### Problem: Screen reader not announcing updates\n\n**Symptoms**: Dynamic content changes but no announcement\n**Cause**: No aria-live region\n**Solution**: Wrap dynamic content in `\u003Cdiv aria-live=\"polite\">` or use role=\"alert\"\n\n### Problem: Dialog focus escapes to background\n\n**Symptoms**: Tab key navigates to elements behind dialog\n**Cause**: No focus trap\n**Solution**: Implement focus trap (see Pattern 1 above)\n\n### Problem: Form errors not announced\n\n**Symptoms**: Visual errors appear but screen reader doesn't notice\n**Cause**: No aria-invalid or role=\"alert\"\n**Solution**: Use aria-invalid + aria-describedby pointing to error message with role=\"alert\"\n\n---\n\n## Complete Setup Checklist\n\nUse this for every page\u002Fcomponent:\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 `\u003Chtml 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\n---\n\n**Questions? Issues?**\n\n1. Check `references\u002Fwcag-checklist.md` for complete requirements\n2. Use `\u002Fa11y-auditor` agent to scan your page\n3. Run axe DevTools for automated testing\n4. Test with actual keyboard + screen reader\n\n---\n\n**Standards**: WCAG 2.1 Level AA\n**Testing Tools**: axe DevTools, Lighthouse, NVDA, VoiceOver\n**Success Criteria**: 90+ Lighthouse score, 0 critical violations\n",{"name":5,"description":7},"main","f4cb2d49f7667e79ef7804f50396d5aaffdf1f56","网页与前端开发\u002Faccessibility",1,"https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification","https:\u002F\u002Fgithub.com\u002FKernel8901","a little little little dobe in life but science geek","2026-04-05 16:43:56",{"source":8,"skillName":5,"commands":56,"prompt":66,"zipAvailable":67},[57,60,63],{"tool":58,"command":59},"npx","npx skills add Kernel8901\u002Fai-agent-skills-classification --skill accessibility",{"tool":61,"command":62},"bunx","bunx skills add Kernel8901\u002Fai-agent-skills-classification --skill accessibility",{"tool":64,"command":65},"pnpm","pnpm dlx skills add Kernel8901\u002Fai-agent-skills-classification --skill accessibility","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F网页与前端开发\u002Faccessibility\u002FSKILL.md",true,[69,75,79,83,87,91,95,99,103,107,111],{"path":70,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":73,"sourceHtmlUrl":74},"_meta.json","REFERENCE","参考文档",290,"https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F网页与前端开发\u002Faccessibility\u002F_meta.json",{"path":76,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":77,"sourceHtmlUrl":78},".claude-plugin\u002Fplugin.json",665,"https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F网页与前端开发\u002Faccessibility\u002F.claude-plugin\u002Fplugin.json",{"path":80,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":81,"sourceHtmlUrl":82},"agents\u002Fa11y-auditor.md",9398,"https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F网页与前端开发\u002Faccessibility\u002Fagents\u002Fa11y-auditor.md",{"path":84,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":85,"sourceHtmlUrl":86},"README.md",7526,"https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F网页与前端开发\u002Faccessibility\u002FREADME.md",{"path":88,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":89,"sourceHtmlUrl":90},"references\u002Faria-patterns.md",11871,"https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F网页与前端开发\u002Faccessibility\u002Freferences\u002Faria-patterns.md",{"path":92,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":93,"sourceHtmlUrl":94},"references\u002Fcolor-contrast.md",8479,"https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F网页与前端开发\u002Faccessibility\u002Freferences\u002Fcolor-contrast.md",{"path":96,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":97,"sourceHtmlUrl":98},"references\u002Ffocus-management.md",11367,"https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F网页与前端开发\u002Faccessibility\u002Freferences\u002Ffocus-management.md",{"path":100,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":101,"sourceHtmlUrl":102},"references\u002Fforms-validation.md",11326,"https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F网页与前端开发\u002Faccessibility\u002Freferences\u002Fforms-validation.md",{"path":104,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":105,"sourceHtmlUrl":106},"references\u002Fsemantic-html.md",15198,"https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F网页与前端开发\u002Faccessibility\u002Freferences\u002Fsemantic-html.md",{"path":108,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":109,"sourceHtmlUrl":110},"references\u002Fwcag-checklist.md",14143,"https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F网页与前端开发\u002Faccessibility\u002Freferences\u002Fwcag-checklist.md",{"path":112,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":113,"sourceHtmlUrl":114},"rules\u002Faccessibility.md",8280,"https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F网页与前端开发\u002Faccessibility\u002Frules\u002Faccessibility.md",[116,139,173,184,194,224],{"skillNo":117,"name":118,"nameZh":6,"description":119,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":120,"categories":121,"keywords":122,"publishedAt":42},"01M6Q5R6WZ06RRB9RQTW54B34B","zyfai","Earn yield on any Ethereum wallet on Base, Arbitrum, and Plasma. Use when a user wants passive DeFi yield on their funds. Deploys a non-custodial subaccount (Safe) linked to their EOA, enables automated yield optimization, and lets them deposit\u002Fwithdraw anytime.","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fzyfai-sdk\u002FSKILL.md",[],[118,123,124,125,126,127,38,128,129,130,131,132,133,134,135,136,137,138],"safe","ethereum","defi","Optimization","user","Wallet","base","deposit","withdraw","yield","arbitrum","non-custodial","passive","defi yield","earn","yield optimization",{"skillNo":140,"name":141,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":142,"categories":143,"keywords":155,"publishedAt":42},"01M6Q5R337B5JSNER4P4YS03TN","zerion-api","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fzerion-api\u002FSKILL.md",[144,147,150],{"categoryNo":145,"categoryName":146,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000010","后端与接口",{"categoryNo":148,"categoryName":149,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":151,"categoryName":152,"taxonomyType":153,"taxonomyTypeDesc":154},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[156,157,158,159,160,161,162,125,163,128,164,165,166,167,168,169,170,171,172],"api","zerion api","zerion","MCP","mcp server","server","blockchain","transactions","crypto wallet","crypto","evm","solana","query","across","positions","token","prices",{"skillNo":174,"name":175,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":176,"categories":177,"keywords":181,"publishedAt":42},"01M6Q5QZFYTDAZMZ27BN5VZXAR","zerion-api-skill","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fzerion-api-skill\u002FSKILL.md",[178,179,180],{"categoryNo":145,"categoryName":146,"taxonomyType":20,"taxonomyTypeDesc":21},{"categoryNo":148,"categoryName":149,"taxonomyType":20,"taxonomyTypeDesc":21},{"categoryNo":151,"categoryName":152,"taxonomyType":153,"taxonomyTypeDesc":154},[156,182,183,157,158,159,160,161,162,125,163,128,165,166,167,168,169,170,171,172],"api skill","zerion api skill",{"skillNo":185,"name":186,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":187,"categories":188,"keywords":192,"publishedAt":42},"01M6Q5QW17QW98RA3FE45E4RD8","zerion-api-mcp","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fzerion-api-mcp\u002FSKILL.md",[189,190,191],{"categoryNo":145,"categoryName":146,"taxonomyType":20,"taxonomyTypeDesc":21},{"categoryNo":148,"categoryName":149,"taxonomyType":20,"taxonomyTypeDesc":21},{"categoryNo":151,"categoryName":152,"taxonomyType":153,"taxonomyTypeDesc":154},[159,156,193,157,158,160,161,125,163,128,164,165,166,167,168,169,170,171,172],"zerion api mcp",{"skillNo":195,"name":196,"nameZh":6,"description":197,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":198,"categories":199,"keywords":203,"publishedAt":42},"01M6Q5QQYZB7A501BDEQWKGSJT","yahoo-finance-forex","Fetches real-time FOREX news and market data from Yahoo Finance for major currency pairs (EUR\u002FUSD, GBP\u002FUSD, USD\u002FJPY, etc.). Analyzes sentiment and provides trading context.","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fyahoo-finance-forex\u002FSKILL.md",[200],{"categoryNo":201,"categoryName":202,"taxonomyType":153,"taxonomyTypeDesc":154},"01JBSEEDCAT000000000000040","财务",[204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223],"finance","yahoo finance","forex","yahoo","yahoo finance forex","github","https","sentiment","real-time","python3","install","Market","usd","news","time","currency","gbp","pip","market data","for",{"skillNo":225,"name":226,"nameZh":6,"description":227,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":228,"categories":229,"keywords":231,"publishedAt":42},"01M6Q5QM6VS84WZVM7BKXXPF32","yahoo-finance","This skill should be used when the user asks to \"get stock prices\", \"check stock quotes\", \"look up earnings\", \"get financial data\", \"find trending stocks\", or needs stock market data from Yahoo Finance.","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fyahoo-finance-cli\u002FSKILL.md",[230],{"categoryNo":201,"categoryName":202,"taxonomyType":153,"taxonomyTypeDesc":154},[204,205,207,232,233,234,235,236,237,215,238,239,240,241,242,243,244,245,172,222],"cli","homebrew","find","check","brew","up","local","get","stock","link","trending","sf","quotes","ln"]