WCAG 2.2 AA audit and contrast validation
epic-expense-type-selection-user-interface-task-013 — Run a full WCAG 2.2 AA audit on both the ExpenseTypePickerWidget and ExpenseCalculationPreview using the accessibility-test-harness and contrast-ratio-validator. Verify touch target sizes (minimum 44×44 dp), colour contrast ratios (minimum 4.5:1 for normal text), focus order, and that all interactive elements have accessible names. Fix any violations found.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 7 - 84 tasks
Can start after Tier 6 completes
Implementation Notes
Start by running Flutter's built-in `SemanticsDebugger` overlay in a debug build and manually tab through both widgets with keyboard navigation enabled. Use `Semantics(label: '...', button: true, child: ...)` wrappers wherever Flutter does not auto-generate correct labels (e.g., custom-painted widgets, icon-only buttons). For touch targets smaller than 44dp, wrap in a `GestureDetector` or `InkWell` with explicit `minimumSize` or use a `SizedBox` constraint — do not resize visible UI, use `Padding` or `ConstrainedBox` instead. For contrast: extract all colour pairs used in these two widgets from the design token system and compute ratios programmatically using the WCAG luminance formula.
Fix by adjusting token values, not by hardcoding hex values. Avoid using `ExcludeSemantics` on elements that carry meaning. Particular attention required for disabled/muted expense types — grey text on dark backgrounds often fails 4.5:1.
Testing Requirements
Write flutter_test widget tests using `tester.getSemantics()` to assert Semantics labels are present and non-empty for every interactive element. Use `findsWidgets` with `SemanticsFlag.isButton` and `SemanticsFlag.isFocusable`. Assert touch target sizes with `tester.getSize(find.byType(X)).width >= 44` (logical pixels). Use a contrast-checking utility or manually verify design token colour pairs against WCAG 4.5:1 ratio with a script.
Run the full test suite on both light and dark themes. Include regression tests that prevent future regressions by asserting Semantics labels are not empty strings.
If the expense calculation preview subscribes to the full BLoC state stream, every unrelated state property change (e.g. a loading flag toggle) triggers a widget rebuild. With complex card animations for the disabled-state transition, this could cause frame drops on low-end Android devices used by some peer mentors.
Mitigation & Contingency
Mitigation: Use select() on the Riverpod provider to subscribe only to the specific state slice each widget needs; write a performance test asserting rebuild count on a rapid sequence of toggle events.
Contingency: If jank is detected in device testing, replace animated disabled-state transitions with instant opacity changes and defer animation polish to a follow-up sprint.
The disabled card state requires a specific contrast-safe colour combination that communicates unavailability without relying solely on colour (WCAG 1.4.1). If the current design token palette does not include a disabled-state token with sufficient contrast for text on the disabled background, the widget will either fail WCAG AA or require a last-minute design token addition that could break other components.
Mitigation & Contingency
Mitigation: Audit the existing design token manifest for disabled-state tokens at the start of the epic; if missing, raise with the design lead and add a contrast-validated token before widget implementation begins.
Contingency: If no design review is available, use the established --color-text-disabled and --color-surface-disabled tokens with an added strikethrough or lock icon to satisfy WCAG 1.4.1 non-colour requirement, and document the deviation for design review.