--- name: framework-accessibility description: Framework-specific accessibility patterns, common pitfalls, and code fix templates for React, Next.js, Vue, Angular, Svelte, and Tailwind CSS. Use when generating framework-aware accessibility fixes or checking framework-specific anti-patterns. --- # Framework-Specific Accessibility Patterns ## React / Next.js ### Common Pitfalls | Pattern | Issue | Fix | |---------|-------|-----| | `onClick` on `
` | Not keyboard accessible | Use ` // Bad: image without alt in Next.js // Good: image with alt Team collaborating in a modern office // Bad: no focus management on route change useEffect(() => { // nothing }, [location]); // Good: focus management on route change useEffect(() => { const mainContent = document.getElementById('main-content'); if (mainContent) { mainContent.focus(); mainContent.scrollIntoView(); } }, [location]); // Bad: link opening new tab Resource // Good: link with new tab warning Resource (opens in new tab) ``` ## Vue ### Common Pitfalls | Pattern | Issue | Fix | |---------|-------|-----| | `v-html` with user content | May inject inaccessible markup | Sanitize and audit injected HTML | | `v-if` on live regions | Removes element from DOM, breaks announcements | Use `v-show` for live regions instead | | `` without focus | Focus lost when content transitions | Manage focus in `@after-enter` hook | | `` to body | Content outside app landmark tree | Add landmark roles to teleported content | ### Fix Templates ```vue
{{ message }}
{{ message }}
``` ## Angular ### Common Pitfalls | Pattern | Issue | Fix | |---------|-------|-----| | `[aria-label]` binding | Invalid - ARIA is not a property | Use `[attr.aria-label]` | | `*ngFor` without `trackBy` | Focus loss on list re-render | Add `trackBy` function | | No `LiveAnnouncer` | Route changes not announced | Inject `LiveAnnouncer` and announce navigation | | `OnPush` + live regions | Change detection may not trigger | Use `ChangeDetectorRef.markForCheck()` | ### Fix Templates ```typescript // Bad: ARIA binding // Good: ARIA attribute binding // Bad: ngFor without trackBy
  • {{ item.name }}
  • // Good: ngFor with trackBy
  • {{ item.name }}
  • // Route change announcements constructor(private liveAnnouncer: LiveAnnouncer, private router: Router) { this.router.events.pipe( filter(event => event instanceof NavigationEnd) ).subscribe((event: NavigationEnd) => { this.liveAnnouncer.announce(`Navigated to ${this.getPageTitle()}`); }); } ``` ## Svelte ### Common Pitfalls | Pattern | Issue | Fix | |---------|-------|-----| | `{#if}` without focus management | Focus lost when content appears | Use `use:action` to focus new content | | `transition:` without motion check | Animations play regardless of user preference | Add `prefers-reduced-motion` check | | `on:click` on non-interactive | Not keyboard accessible | Use `
    Content
    Content
    ``` ## Tailwind CSS ### Common Pitfalls | Pattern | Issue | Fix | |---------|-------|-----| | `outline-none` | Removes focus indicator | Pair with `ring-2 ring-offset-2 focus-visible:ring-blue-500` | | `text-gray-400` on `bg-white` | Fails 4.5:1 contrast | Use `text-gray-600` or darker | | No `motion-reduce:` variant | Animations ignore user preference | Add `motion-reduce:transition-none` | | Missing `focus:` styles | No visible focus indicator | Add `focus:ring-2 focus:ring-blue-500` | | `sr-only` missing | Screen reader text not available | Add `description` | ### Contrast-Safe Tailwind Pairs | Background | Minimum Text | Ratio | |-----------|-------------|-------| | `bg-white` | `text-gray-600` | 4.55:1 | | `bg-white` | `text-gray-700` | 6.62:1 | | `bg-gray-50` | `text-gray-700` | 6.29:1 | | `bg-gray-900` | `text-gray-300` | 5.92:1 | | `bg-blue-600` | `text-white` | 5.23:1 | | `bg-red-600` | `text-white` | 4.54:1 | | `bg-green-700` | `text-white` | 4.58:1 | ### Fix Templates ```html

    Important information

    Important information

    Card
    Card
    ```