,
+ document.body
+ );
+}
+```
+
+**Key patterns per ARIA APG:**
+- Focus the **first meaningful element** inside the dialog, not the dialog itself
+- Use `aria-describedby` for additional context beyond the title
+- Always restore focus to the previously focused element when closing
+
+### Dynamic Content and Live Regions
+
+Per [ARIA APG Live Regions](https://www.w3.org/WAI/ARIA/apg/practices/live-regions/):
+
+```jsx
+// Polite updates (announced when user is idle)
+// Always use aria-atomic="true" to announce entire region, not just changed parts
+function StatusMessage({ message }) {
+ return (
+
{/* Screen reader users will miss structure */}
+
+
+ );
+}
+```
+
+## Testing Checklist
+
+### Automated Testing
+
+- Run axe-core or Lighthouse accessibility audits
+- Verify no critical accessibility errors
+- Check color contrast passes
+
+### Keyboard Testing
+
+1. Press `Tab` to move through all interactive elements
+2. Verify focus indicator is visible on every element
+3. Press `Enter`/`Space` to activate buttons and links
+4. Test modal opens and focuses correctly
+5. Verify `Escape` closes modals/dropdowns
+6. Check no keyboard traps (can always move focus forward/backward)
+
+### Screen Reader Testing
+
+Test with at least one of:
+- **VoiceOver** (macOS/iOS) - `Cmd + F5`
+- **NVDA** (Windows) - `Insert + Down Arrow`
+- **JAWS** (Windows)
+- **TalkBack** (Android)
+
+Check:
+- All images have alt text
+- Form fields are labeled
+- Headings form a logical outline
+- Links have meaningful text
+- Dynamic content is announced
+
+### Touch Target Testing
+
+- Verify all interactive elements are at least 44x44px
+- Check spacing between adjacent touch targets
+- Test on actual mobile device when possible
+
+### Reduced Motion Testing
+
+1. Enable "Reduce Motion" in OS settings
+2. Visit your application
+3. Verify:
+ - No unnecessary animations play
+ - All functionality remains usable
+ - No animations that could cause discomfort
+
+## Resources
+
+- [WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
+- [WCAG 2.2 Quick Reference](https://www.w3.org/WAI/WCAG22/quickref/)
+- [ARIA Authoring Practices Guide (APG)](https://www.w3.org/WAI/ARIA/apg/)
+- [ARIA APG - Dialog Modal Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)
+- [ARIA APG - Names and Descriptions](https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/)
+- [ARIA APG - Landmark Regions](https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/)
+- [ARIA APG - Live Regions](https://www.w3.org/WAI/ARIA/apg/practices/live-regions/)
+- [A11y Project Checklist](https://www.a11yproject.com/checklist/)
+- [MDN Accessibility Docs](https://developer.mozilla.org/en-US/docs/Web/Accessibility)
+- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
+- [Framer Motion Reduced Motion](https://www.framer.com/motion/reduce-motion/)
diff --git a/skills/frontend-accessibility/SKILL.md b/skills/frontend-accessibility/SKILL.md
new file mode 100644
index 0000000..ccc695c
--- /dev/null
+++ b/skills/frontend-accessibility/SKILL.md
@@ -0,0 +1,540 @@
+---
+name: frontend-accessibility
+description: Enforce WCAG 2.1 AA compliance in visual UI code. Use when generating React/Next.js components, implementing animations, or reviewing designs for accessibility barriers.
+---
+
+# Frontend Accessibility (WCAG 2.1 AA + 2.2)
+
+## Core Principle
+
+**Accessibility is not optional. Every user deserves equal access to your product.**
+
+## When to Use
+
+This skill should be invoked when:
+
+- Generating new React/Next.js components
+- Implementing animations, transitions, or motion effects
+- Creating interactive elements (buttons, forms, modals)
+- Reviewing existing code for accessibility barriers
+
+## Motion Safety Rules
+
+### Respect User Motion Preferences
+
+Always implement `prefers-reduced-motion` to honor system-level accessibility settings.
+
+```jsx
+import { motion, useReducedMotion } from 'framer-motion';
+
+export function AnimatedCard({ children }) {
+ const shouldReduceMotion = useReducedMotion();
+
+ return (
+
+ {children}
+
+ );
+}
+```
+
+### Safe Animation Patterns
+
+- **Duration**: Keep animations under 300ms for micro-interactions
+- **Flashing**: Never create content that flashes more than 3 times per second
+- **Scroll**: Disable parallax effects when reduced motion is preferred
+
+## Keyboard & Focus Management
+
+### Skip Links
+
+Every page must have a skip link as the first focusable element:
+
+```jsx
+export function SkipLink() {
+ return (
+
+ Skip to main content
+
+ );
+}
+```
+
+**Pure CSS alternative (no Tailwind required):**
+
+```css
+.skip-link {
+ position: absolute;
+ left: -9999px;
+ z-index: 9999;
+ padding: 1rem;
+ background: white;
+ color: black;
+ text-decoration: none;
+}
+
+.skip-link:focus {
+ left: 0;
+ top: 0;
+}
+```
+
+```jsx
+
+ Skip to main content
+
+```
+
+### Focus Indicators
+
+Never remove focus indicators. Customize them instead:
+
+```jsx
+/* ✅ Custom focus styles */
+:focus-visible {
+ outline: 2px solid #2563eb;
+ outline-offset: 2px;
+}
+
+/* ❌ Never do this */
+:focus {
+ outline: none;
+}
+```
+
+### Focus Traps
+
+Use focus traps in modals and dialogs. Following [ARIA APG dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/):
+
+```jsx
+import { useRef, useEffect, useCallback } from 'react';
+
+export function useFocusTrap(isActive, onEscape) {
+ const containerRef = useRef(null);
+ const previousFocusRef = useRef(null);
+ const isActiveRef = useRef(isActive);
+
+ useEffect(() => {
+ isActiveRef.current = isActive;
+ }, [isActive]);
+
+ const handleKeyDown = useCallback((e) => {
+ if (e.key === 'Escape') {
+ onEscape?.();
+ return;
+ }
+
+ if (e.key !== 'Tab') return;
+ if (!containerRef.current) return;
+
+ const focusableElements = containerRef.current.querySelectorAll(
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
+ );
+
+ if (focusableElements.length === 0) return;
+
+ const firstElement = focusableElements[0];
+ const lastElement = focusableElements[focusableElements.length - 1];
+
+ if (e.shiftKey) {
+ if (document.activeElement === firstElement || document.activeElement === document.body) {
+ e.preventDefault();
+ lastElement.focus();
+ }
+ } else {
+ if (document.activeElement === lastElement || document.activeElement === document.body) {
+ e.preventDefault();
+ firstElement.focus();
+ }
+ }
+ }, [onEscape]);
+
+ useEffect(() => {
+ if (!isActive || !containerRef.current) return;
+
+ // Store previous focus to restore later
+ previousFocusRef.current = document.activeElement;
+
+ const focusableElements = containerRef.current.querySelectorAll(
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
+ );
+
+ const firstElement = focusableElements[0];
+ // Focus first element - ensure it's visible before focusing
+ firstElement?.focus();
+
+ document.addEventListener('keydown', handleKeyDown);
+
+ return () => {
+ if (!isActiveRef.current) return;
+ document.removeEventListener('keydown', handleKeyDown);
+ // Restore focus when dialog closes
+ previousFocusRef.current?.focus();
+ };
+ }, [isActive, handleKeyDown]);
+
+ return containerRef;
+}
+```
+
+**Usage:**
+```jsx
+function Modal({ isOpen, onClose, title, description, children }) {
+ const modalRef = useFocusTrap(isOpen, onClose);
+
+ if (!isOpen) return null;
+
+ return (
+
+
{title}
+
{description}
+ {children}
+
+
+ );
+}
+```
+
+**Note:** Per [ARIA APG](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/), avoid making the dialog element itself focusable (`tabindex="-1"` on the dialog role). Focus the first meaningful element inside instead.
+
+### Accordion
+
+Use proper ARIA attributes for collapsible sections:
+
+```jsx
+function Accordion({ items }) {
+ return (
+
+ );
+}
+```
+
+**Keyboard navigation for tabs:**
+- Arrow keys to move between tabs
+- Home/End to jump to first/last tab
+- Tab to move to the active tab panel
+
+## Screen Reader Support
+
+### Semantic HTML
+
+Use the right element for the right purpose:
+
+```jsx
+// ✅ Semantic - screen readers understand the structure
+
+
+// ❌ Non-semantic - loses meaning
+
+```
+
+### Landmark Regions
+
+Use semantic elements and ARIA landmarks to help screen reader users navigate. Per [ARIA APG landmark regions](https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/):
+
+```jsx
+{/* ✅ Use native HTML5 elements (recommended) */}
+
+
+
+
+
+ {/* primary content */}
+
+
+
+
+
+
+{/* ✅ Multiple nav elements need unique labels */}
+
+
+
+```
+
+**Note:** The `` element should have an ID for skip links to target (`id="main-content"`).
+
+### ARIA Labels (When Needed)
+
+Only use ARIA when semantic HTML isn't sufficient:
+
+```jsx
+// ✅ Icon button needs aria-label
+
+
+// ✅ Input needs aria-describedby for error context
+
+
+ Please enter a valid email address
+
+```
+
+### Live Regions
+
+Announce dynamic content changes. Per [ARIA APG practices](https://www.w3.org/WAI/ARIA/apg/practices/live-regions/):
+
+```jsx
+// ✅ Status updates are announced (polite = waits for idle)
+
+ {statusMessage}
+
+
+// ✅ Errors are announced immediately (assertive = interrupts)
+
+
+// ✅ Loading states
+
+
+
+```
+
+**Note:** Always add `aria-atomic="true"` to ensure the entire region is announced, not just the changed portion.
+
+## Visual Accessibility
+
+### Color Contrast (4.5:1 Minimum)
+
+Ensure sufficient contrast for text:
+
+- **Normal text**: 4.5:1 ratio (AA standard)
+- **Large text** (18px+ or 14px bold): 3:1 ratio (still criterion 1.4.3)
+- **UI components**: 3:1 ratio against adjacent colors
+
+```jsx
+// Use semantic color tokens from your design system
+// that automatically meet contrast requirements
+
+```
+
+### Touch Targets
+
+Minimum 44x44 pixels for interactive elements:
+
+```jsx
+// ✅ Large enough touch target
+
+
+// ❌ Too small - difficult to tap
+
+```
+
+### Form Labels
+
+Associate labels with inputs for screen readers. Per [ARIA APG naming practices](https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/):
+
+```jsx
+// ✅ Explicit association (preferred)
+
+
+
+// ✅ Implicit association (wrapped)
+
+
+// ✅ Complex fields with aria-labelledby
+
+First and last name
+
+// ✅ Form errors - use aria-describedby (better support than aria-errormessage)
+
+{hasError && (
+
+ Please enter a valid email address
+
+)}
+```
+
+### Color Independence
+
+Never convey information by color alone:
+
+```jsx
+// ✅ Color + icon/text
+
+ Required field
+
+
+// ❌ Color only - not accessible
+
+ Required field
+
+```
+
+## Definition of Done Checklist
+
+Before marking accessibility work as complete, verify:
+
+- [ ] All interactive elements are keyboard accessible
+- [ ] Focus indicators are visible on all focusable elements
+- [ ] Skip link present and functional
+- [ ] Color contrast meets 4.5:1 (text) or 3:1 (large text/components)
+- [ ] Touch targets are minimum 44x44px
+- [ ] No information conveyed by color alone
+- [ ] `prefers-reduced-motion` respected in all animations
+- [ ] ARIA labels used correctly (not overused)
+- [ ] Semantic HTML used throughout
+- [ ] Form inputs have associated labels
+- [ ] Error messages are announced to screen readers
+- [ ] Focus trapped in modals/dialogs
+- [ ] No content flashes more than 3 times per second
+
+## Quick Reference
+
+| Requirement | WCAG Criterion | Minimum |
+|-------------|----------------|---------|
+| Color contrast (text) | 1.4.3 | 4.5:1 |
+| Color contrast (large text) | 1.4.3 | 3:1 |
+| Focus visible | 2.4.7 | Visible indicator |
+| Skip link | 2.4.1 | Bypass blocks |
+| Touch target | 2.5.5 | 44x44px |
+| Motion | 2.3.3 | Respect preference |
+| Error identification | 3.3.1 | Announced |
+
+### WCAG 2.2 New Additions
+
+| Requirement | WCAG Criterion | Description |
+|-------------|----------------|-------------|
+| Focus not obscured | 2.4.11 | Focused element fully visible |
+| Redundant entry | 3.3.7 | Don't re-enter info already provided |
+
+## See Also
+
+- [REFERENCE.md](./REFERENCE.md) - Detailed WCAG checklists and deep-dive documentation
+
+## Sources
+
+- [ARIA Authoring Practices Guide (APG)](https://www.w3.org/WAI/ARIA/apg/) - Official W3C patterns for accessible widgets
+- [WCAG 2.2 Quick Reference](https://www.w3.org/WAI/WCAG22/quickref/) - Accessibility guidelines
+- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/) - Contrast verification tool
+- [A11y Project Checklist](https://www.a11yproject.com/checklist/) - Practical accessibility checklist