# WCAG 2.1 AA Accessibility Implementation Design ## Overview Implement WCAG 2.1 AA compliance app-wide, ensuring accessibility for users with visual impairments, motor disabilities, and those using assistive technologies like VoiceOver. ## Scope **In scope:** - Color contrast certification for Monochrome theme (light + dark modes) - VoiceOver support for all view files - accessibility labels, hints, and traits - Theme picker update - accessible badge on Monochrome - Touch targets - minimum 44x44pt for all interactive elements - Focus management - logical focus order, visible focus indicators - Developer guidelines - patterns and rules for future development **Out of scope:** - Certifying the other 6 themes (kept as "visual preference" options) - PDF export accessibility (separate concern, fixed dimensions) - Motion reduction (iOS handles via system setting) ## Decisions 1. **Default theme**: Monochrome is the guaranteed-compliant theme 2. **Other themes**: Kept unchanged, no warnings - only Monochrome shows "✓ Accessible" badge 3. **Color changes**: Fix Monochrome palette for all users (no conditional/toggle) 4. **Coverage**: Full accessibility across all screens ## Monochrome Theme Color Fixes ### Current Values | Role | Dark Mode | Light Mode | |------|-----------|------------| | Text muted | `#707070` | `#707070` | | Primary accent | `#808080` | `#808080` | | Background 1 | `#121212` | `#FAFAFA` | | Card background | `#1C1C1C` | `#FFFFFF` | ### Issues - Text muted (`#707070`) on dark card (`#1C1C1C`) = ~3.5:1 (needs 4.5:1) ### Fixes | Role | Dark Mode | Light Mode | |------|-----------|------------| | Text muted | `#707070` → `#8A8A8A` | `#707070` → `#595959` | New contrast ratios: - Dark: `#8A8A8A` on `#1C1C1C` = ~5.1:1 ✓ - Light: `#595959` on `#FFFFFF` = ~7:1 ✓ ## VoiceOver Support Strategy ### Element Labeling | Element Type | Modifier | Example | |--------------|----------|---------| | Buttons | `.accessibilityLabel()` | "Create trip" | | Icons with meaning | `.accessibilityLabel()` | "Baseball" for MLB icon | | Decorative images | `.accessibilityHidden(true)` | Background gradients | | Status indicators | `.accessibilityLabel()` + `.accessibilityValue()` | "Progress: 12 of 30 stadiums visited" | | Toggle/Picker | `.accessibilityLabel()` + `.accessibilityHint()` | Label: "Theme", Hint: "Double tap to change" | | Cards (tappable) | `.accessibilityElement(children: .combine)` | Combines child text into single announcement | | Lists | `.accessibilityLabel()` per row | "Yankees at Red Sox, April 15, 7:05 PM" | ### Naming Conventions - **Labels** describe what it IS: "Create trip button" - **Hints** describe what happens: "Opens trip planner" - **Values** describe current state: "Selected" or "3 of 5" ### Files to Modify (20 view files) **Home:** - `HomeView.swift` - Tab bar, suggested trip cards, quick actions - `SuggestedTripCard.swift` - Card accessibility - `LoadingTripsView.swift` - Loading state announcements **Trip:** - `TripCreationView.swift` - Sport picker, date picker, region selector, mode selector - `TripDetailView.swift` - Timeline items, game cards, travel segments, export button - `RegionMapSelector.swift` - Region selection - `TimelineItemView.swift` - Timeline accessibility **Schedule:** - `ScheduleListView.swift` - Game list rows, sport filter, date navigation **Progress:** - `ProgressTabView.swift` - Tab navigation - `ProgressMapView.swift` - Stadium map, markers - `VisitDetailView.swift` - Visit information - `StadiumVisitSheet.swift` - Stadium details - `AchievementsListView.swift` - Achievement badges - `GameMatchConfirmationView.swift` - Confirmation dialog - `PhotoImportView.swift` - Photo selection **Settings:** - `SettingsView.swift` - All rows, theme picker with accessible badge **Theme:** - `ViewModifiers.swift` - Reusable component accessibility - `AnimatedComponents.swift` - Animated element accessibility - `SportSelectorGrid.swift` - Sport grid accessibility **App:** - `SportsTimeApp.swift` - Root accessibility configuration ## Theme Picker UI Update `SettingsView.swift` theme section: - Monochrome shows "✓ Accessible" badge beneath theme name - Other 6 themes display normally with no indicators ``` ┌─────────────────────────────────────┐ │ Theme │ ├─────────────────────────────────────┤ │ [███] Monochrome │ │ ✓ Accessible │ ├─────────────────────────────────────┤ │ [███] Teal │ ├─────────────────────────────────────┤ │ [███] Orbit │ └─────────────────────────────────────┘ ``` ## Touch Targets Minimum 44x44pt hit area for all interactive elements. **Areas to audit:** - Icon-only buttons (export, share, close) - Inline links - Compact list rows **Fix pattern:** ```swift Button(action: { }) { Image(systemName: "square.and.arrow.up") } .frame(minWidth: 44, minHeight: 44) ``` ## Focus Management SwiftUI defaults are sufficient. Ensure: - Logical tab order (top-to-bottom, left-to-right) - Focus indicators visible (system default rings) - Modal dismissal returns focus to trigger - No keyboard traps ## Developer Guidelines Create `docs/ACCESSIBILITY.md` with: ### Color Rules - Monochrome theme: verify 4.5:1 contrast for text, 3:1 for UI components - Don't rely solely on color to convey meaning (add icons, text, or patterns) ### Accessibility Label Rules - Every `Button` needs `.accessibilityLabel()` unless text is self-descriptive - Every meaningful `Image` needs `.accessibilityLabel()` - Decorative images get `.accessibilityHidden(true)` - Group related elements with `.accessibilityElement(children: .combine)` ### Touch Target Rules - All tappable elements minimum 44x44pt hit area - Use `.contentShape(Rectangle())` to expand hit areas when needed ### Testing Requirements - Enable VoiceOver and navigate every new screen before PR - Test with Dynamic Type at Accessibility XXL - Test Monochrome theme in both light and dark modes ### PR Checklist ``` ## Accessibility - [ ] All new buttons have accessibility labels - [ ] Images labeled or hidden appropriately - [ ] Touch targets are 44x44pt minimum - [ ] Tested with VoiceOver - [ ] Tested with large Dynamic Type ``` ## Testing Strategy ### Manual Testing with Accessibility Inspector - Xcode → Open Developer Tool → Accessibility Inspector - Audit each screen for contrast warnings and missing labels ### VoiceOver Testing (per screen) 1. Enable VoiceOver (Settings → Accessibility → VoiceOver) 2. Navigate entire screen with swipe gestures 3. Verify all interactive elements are announced correctly 4. Verify logical reading order 5. Test activation (double-tap) of buttons and controls ### Dynamic Type Testing - Settings → Accessibility → Display & Text Size → Larger Text - Test at default, Large, and Accessibility XXL sizes - Check for truncation, overlap, or layout breaks ### Contrast Validation - Use WebAIM Contrast Checker or similar tool - Validate all Monochrome text/background combinations - Document final contrast ratios in ACCESSIBILITY.md ## Implementation Order 1. Fix Monochrome theme colors in `Theme.swift` 2. Update `SettingsView.swift` theme picker with accessible badge 3. Add accessibility labels to all 20 view files (alphabetically by feature) 4. Audit touch targets and fix undersized elements 5. Create `docs/ACCESSIBILITY.md` with guidelines 6. Full VoiceOver testing pass 7. Full contrast validation pass