- Skip-to-main-content link present in
app/layout.tsx:24-29 - Hidden by default, visible on focus for keyboard users
- Links to
#main-contentanchor present on all page layouts
- All interactive elements are focusable
- Focus-visible indicators added via
globals.csswith 2px outline + 4px ring - Mobile navigation drawer includes focus trap (Tab cycling)
- Escape key closes mobile navigation drawer
- Minimum touch target size of 44x44px on interactive elements
- Navigation landmarks labeled:
aria-label="Main navigation"on Header, Sidebar - Mobile navigation:
role="dialog",aria-modal="true",aria-label="Mobile navigation" - Buttons with icon-only content include
aria-label(Copy, QR Code, Theme toggle, etc.) - Search form includes
role="search"andaria-label="Site search" - Form inputs include
aria-labelattributes - Decorative icons marked with
aria-hidden="true" - Error display includes
role="alert"for screen reader announcements - Live regions added for dynamic content (
aria-live="polite")
- Landing page: h1 (headline) → h2 (features section) → h3 (feature cards)
- Status page: h1 (System Status) → h2 (Services, Incident History, Subscribe)
- All pages use semantic heading levels without skipping
- Auth layout Terms/Privacy links updated to use Next.js Link components
- Form inputs have associated labels via
aria-label - Required fields indicated with
requiredattribute - Error states communicated via
role="alert"
- Text colors use CSS custom properties for theme-aware contrast
- Primary text:
--foreground: #0F172Aon white background (contrast > 7:1) - Muted text:
--muted-foreground: #64748Bon white (contrast > 4.5:1) - Interactive elements have minimum 3:1 contrast ratio
- Focus indicators use
--ringcolor for consistent visibility
- Logo images have appropriate
alttext oralt=""for decorative - Decorative icons use
aria-hidden="true" - Background decorations marked as
aria-hidden="true"
- Toast notifications via Sonner library (accessible by default)
- Loading states include
role="status"with screen reader text - Status page auto-refresh announced via
aria-live="polite"
components/layout/Header.tsx— Addedaria-labelto nav, fixed broken anchor linkscomponents/layout/Footer.tsx— Addedaria-labelto icon links, made status indicator a linkcomponents/layout/MobileBottomNav.tsx— Addedaria-current="page"for active statescomponents/layout/MobileNavDrawer.tsx— Added focus trap, keyboard handling,aria-hiddenon iconscomponents/shared/EmptyState.tsx— Addedrole="status",aria-hiddenon decorative iconcomponents/shared/ErrorDisplay.tsx— Addedrole="alert", fixed color contrast to use destructive variantapp/auth/layout.tsx— Addedaria-hiddenon decorative bg, fixed Terms/Privacy linksapp/auth/register/page.tsx— Addedrole="status",aria-live="polite",aria-hiddenon spinnerapp/page.tsx— Addedaria-hiddenon decorative elements,role="list"on feature cardsapp/globals.css— Enhanced focus-visible styles for WCAG complianceapp/status/page.tsx— Addedaria-labelledbyfor section headingscomponents/status/OverallBanner.tsx— Addedrole="status",aria-live="polite"components/status/SubscribeForm.tsx— Addedaria-labelon email input
- Automated Testing: Run axe-core accessibility scanner
- Keyboard Testing: Tab through all pages, verify focus order and visibility
- Screen Reader Testing: Test with VoiceOver (macOS) or NVDA (Windows)
- Color Contrast: Verify with WebAIM Contrast Checker
- Mobile Testing: Verify touch targets and mobile navigation on iOS/Android
- Third-party components (Google OAuth, Stellar Wallet) may have their own accessibility characteristics
- Charts (Recharts) provide limited built-in accessibility; consider adding data tables as alternatives
- PDF invoice generation (jsPDF) may not produce accessible PDFs