critical priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

All method cards (BankID, Vipps, Biometric) have Semantics widgets with descriptive labels that include the method name and purpose (e.g., 'Sign in with BankID, secure Norwegian national ID authentication')
All interactive touch targets measure at least 44x44 dp, verified via Flutter widget tests using tester.getSize()
All text elements pass 4.5:1 contrast ratio against their background color, verified with a color contrast checker against the design token palette
Focus order traverses the screen top-to-bottom: screen title → descriptive subtitle → BankID card → Vipps card → help/support link, confirmed via accessibility audit
Screen reader (VoiceOver on iOS, TalkBack on Android) announces a screen-entry message such as 'Authentication Method Selector. Choose how you want to sign in.' when the screen becomes active
Switch-access users can reach and activate every interactive element without touch
No method card or button uses color as the sole differentiator — icons and text labels accompany all color cues
Tapping a method card with VoiceOver active announces the card label and its role (e.g., 'button, double-tap to activate')
The screen passes flutter_test accessibility widget test (SemanticsFlag checks) with zero violations
Dynamic text scaling up to 200% does not overflow or clip any label or card content

Technical Requirements

frameworks
Flutter
Riverpod
performance requirements
Screen must reach first-paint within 300 ms on mid-range Android hardware
Semantics tree must be fully populated before the first frame is rendered to prevent screen reader delay
security requirements
No authentication tokens or user identifiers are present or logged on this screen
Screen content must not be captured by screenshot prevention policies (Flutter SecureScreen or equivalent)
ui components
Semantics widget wrapping each method card with label, hint, and onTapHint
MergeSemantics for compound card elements (icon + title + subtitle)
ExcludeSemantics for purely decorative graphics
FocusTraversalGroup for explicit focus ordering
SemanticsService.announce() call on screen initState for orientation announcement
Design token color values validated at 4.5:1 contrast against WCAG AA threshold

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Wrap the entire method card list in a FocusTraversalGroup using ReadingOrderTraversalPolicy so focus order matches visual layout regardless of widget tree order. Use SemanticsService.announce() inside a post-frame callback (WidgetsBinding.instance.addPostFrameCallback) at screen init to fire the orientation announcement after the screen is rendered — calling it before the frame causes it to be swallowed by some screen readers. For contrast checking, add a dev-only debug flag that overlays contrast ratios using the design token system to catch regressions during development. All Semantics labels must be defined as constants in a dedicated a11y_strings.dart file to enable easy localization and testing.

The Blindeforbundet requirements make screen-reader quality non-negotiable — treat any VoiceOver regression as a P0 bug. Do not rely solely on Flutter's default semantics inheritance; explicitly set label, hint, and value on all custom widgets. Minimum touch target padding should be applied via Semantics(container: true) with an explicit size constraint rather than just increasing visible padding, so the visual design is not impacted.

Testing Requirements

Unit tests: verify Semantics node labels match expected strings for each method card using flutter_test's SemanticsController. Widget tests: assert touch-target sizes are >= 44x44 dp for all interactive widgets using tester.getSize(). Accessibility integration tests: run Flutter's built-in accessibility audit (SemanticsNode tree validation) and assert zero violations. Manual testing: exercise the screen on a physical iPhone with VoiceOver enabled and a physical Android device with TalkBack enabled, verifying announcement text is natural and orientation message fires on entry.

Dynamic type test: set system font scale to 1.0, 1.5, and 2.0 and assert no overflow RenderFlex errors appear in the widget tree.

Epic Risks (3)
high impact medium prob technical

BankID on mobile uses a WebView or external app redirect that has known compatibility issues with Flutter's WebView package on certain Android versions. BankID's JavaScript-heavy broker pages may also trigger CSP or mixed-content errors in a Flutter WebView, preventing the authentication flow from completing.

Mitigation & Contingency

Mitigation: Use the flutter_inappwebview package (more mature than webview_flutter for complex OAuth pages) and validate BankID WebView rendering on the broker's test environment before integrating with the service layer. Prefer external browser redirect where the broker supports it.

Contingency: If WebView approach fails for certain BankID brokers, implement the full external browser redirect + deep link callback pattern as the primary flow and treat WebView as a fallback only.

medium impact medium prob technical

The OAuth redirect flows (both Vipps and BankID) temporarily move the user outside the Flutter app into an external browser or the Vipps/BankID app. Screen reader users may lose focus context during this transition and become disoriented when the app callback returns them to the loading state, failing the WCAG 2.2 AA mandate.

Mitigation & Contingency

Mitigation: Implement explicit accessibility announcements (live region announcements) at each transition point: when launching the external flow ('Opening Vipps'), during the loading wait state ('Waiting for Vipps confirmation'), and on return ('Login successful' or 'Login failed — please try again'). Test with VoiceOver on iOS and TalkBack on Android during development.

Contingency: If OAuth transition accessibility is unresolvable on a specific platform, add an explicit accessibility user guide in the onboarding flow explaining the external app redirect behavior to set user expectations.

low impact high prob technical

Biometric UI varies significantly across devices — Face ID (iPhone), fingerprint sensor (most Android), front-facing camera biometrics (some Android), and devices with no biometrics at all. Flutter's local_auth handles the OS dialog but the surrounding UI must gracefully handle all these cases, and testing coverage for all permutations is difficult.

Mitigation & Contingency

Mitigation: Use local_auth's getAvailableBiometrics() to detect the exact biometric type and render appropriate iconography (Face ID icon vs. fingerprint icon). For devices with no biometrics, skip the biometric screen entirely and route directly to full re-authentication.

Contingency: If a specific device configuration produces unexpected local_auth behavior in production, implement a user-accessible toggle in Settings to disable biometric login entirely, routing those users to the standard BankID/Vipps flow without biometrics.