From e7fb3cfbbe15c6975977870796b1be69687b821d Mon Sep 17 00:00:00 2001 From: Trey t Date: Sun, 11 Jan 2026 11:14:40 -0600 Subject: [PATCH] docs: add WCAG 2.1 AA accessibility design Co-Authored-By: Claude Opus 4.5 --- .../2026-01-11-wcag-accessibility-design.md | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 docs/plans/2026-01-11-wcag-accessibility-design.md diff --git a/docs/plans/2026-01-11-wcag-accessibility-design.md b/docs/plans/2026-01-11-wcag-accessibility-design.md new file mode 100644 index 0000000..ea48245 --- /dev/null +++ b/docs/plans/2026-01-11-wcag-accessibility-design.md @@ -0,0 +1,219 @@ +# 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