Background
src/components/ui/Modal.tsx documents itself as having a focus trap, but it only locks body scroll and closes on Escape -- it never traps Tab focus inside the dialog and never restores focus to the trigger element on close. Several overlays bypass Modal entirely and reimplement dialog markup with the same gaps: src/components/CommandPalette.tsx, src/components/ConflictResolver.tsx, src/components/editor/MediaEmbedder.tsx, src/components/courses/VideoPreview.tsx, src/components/notificationcenter.tsx (its role=dialog dropdown panel), and src/components/CookieConsentBanner.tsx. As a result keyboard and screen-reader users can Tab out of an open dialog into the page behind it, and focus is lost when the dialog closes.
Implementation Plan
- Add a shared hook src/hooks/useFocusTrap.ts that traps Tab / Shift+Tab within the active dialog, restores focus to the opener on unmount, and supports an optional initial-focus target.
- Implement a real focus trap plus focus restore in src/components/ui/Modal.tsx and correct its docstring.
- Apply the hook, plus consistent aria-modal / aria-labelledby wiring and Escape handling, to CommandPalette.tsx, ConflictResolver.tsx, MediaEmbedder.tsx, VideoPreview.tsx, notificationcenter.tsx, and CookieConsentBanner.tsx.
- Add benchmarks/metrics where meaningful (e.g. automated axe/a11y checks before/after).
- Add regression tests (keyboard-navigation and focus-trap tests).
- Document the change.
Files in Scope (8)
src/hooks/useFocusTrap.ts (new), src/components/ui/Modal.tsx, src/components/CommandPalette.tsx, src/components/ConflictResolver.tsx, src/components/editor/MediaEmbedder.tsx, src/components/courses/VideoPreview.tsx, src/components/notificationcenter.tsx, src/components/CookieConsentBanner.tsx
Acceptance Criteria
- Measurable accessibility improvement demonstrated with before/after checks
- No regression in existing functionality
- Tests pass and code follows project standards
- Change is documented
Difficulty
Medium
Background
src/components/ui/Modal.tsx documents itself as having a focus trap, but it only locks body scroll and closes on Escape -- it never traps Tab focus inside the dialog and never restores focus to the trigger element on close. Several overlays bypass Modal entirely and reimplement dialog markup with the same gaps: src/components/CommandPalette.tsx, src/components/ConflictResolver.tsx, src/components/editor/MediaEmbedder.tsx, src/components/courses/VideoPreview.tsx, src/components/notificationcenter.tsx (its role=dialog dropdown panel), and src/components/CookieConsentBanner.tsx. As a result keyboard and screen-reader users can Tab out of an open dialog into the page behind it, and focus is lost when the dialog closes.
Implementation Plan
Files in Scope (8)
src/hooks/useFocusTrap.ts (new), src/components/ui/Modal.tsx, src/components/CommandPalette.tsx, src/components/ConflictResolver.tsx, src/components/editor/MediaEmbedder.tsx, src/components/courses/VideoPreview.tsx, src/components/notificationcenter.tsx, src/components/CookieConsentBanner.tsx
Acceptance Criteria
Difficulty
Medium