critical priority medium complexity frontend pending frontend specialist Tier 5

Acceptance Criteria

All five screens (Auth Method Selector, Vipps Auth, BankID Auth, Biometric Auth, Personnummer Confirmation) have Semantics labels on every interactive and informational element
On each screen's initial load, SemanticsService.announce() or a Semantics(liveRegion: true) widget emits a screen title announcement (e.g., 'Authentication method selection screen')
Loading states emit an announcement (e.g., 'Connecting to BankID, please wait') via a Semantics(liveRegion: true) container that updates when the loading message changes
Error states emit an immediate announcement with the full error message text — the announcement fires within one frame of the error state being emitted by the BLoC, without requiring focus change
Success states emit a confirmation announcement (e.g., 'Authentication successful') before navigation occurs, giving the screen reader time to read it
Primary action elements (buttons, confirm checkbox) receive autofocus or programmatic focus via FocusNode.requestFocus() when the screen or modal loads
All icon-only buttons have a Semantics(label: '...') that describes the action, not the icon name
Decorative images and background graphics are marked with Semantics(excludeSemantics: true) to prevent cluttering the accessibility tree
Tested manually on iOS Simulator with VoiceOver enabled and Android Emulator with TalkBack enabled — all announcements fire correctly
No duplicate announcements: state transitions do not trigger the same announcement twice due to widget rebuilds

Technical Requirements

frameworks
Flutter
BLoC
Flutter Semantics API
apis
SemanticsService.announce()
Semantics widget (liveRegion, label, hint, button, header)
FocusNode.requestFocus()
AutofocusScope
data models
AuthState (loading, error, success, waiting variants for each screen)
performance requirements
Live region announcements must not cause widget tree rebuilds of non-announcement subtrees — isolate in dedicated Semantics wrappers
FocusNode.requestFocus() calls must be scheduled via WidgetsBinding.instance.addPostFrameCallback to avoid focus errors during build
security requirements
Error announcements must not include sensitive data (personnummer, tokens) — only user-facing error message strings
Accessibility tree must not expose internal state identifiers that could reveal system implementation details
ui components
LiveRegionAnnouncer — reusable widget that wraps Semantics(liveRegion: true) and triggers announcement on text change
FocusOnLoad — small helper widget that calls FocusNode.requestFocus() in initState via postFrameCallback
AccessibleErrorMessage — standardized error display widget with liveRegion semantics

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Create a reusable LiveRegionAnnouncer widget that takes a String message and wraps it in Semantics(liveRegion: true, child: Text(message)). Whenever the message changes (driven by BLoC state), the screen reader automatically announces the new text. Use this for loading, error, and success states across all five screens. For initial screen title announcements use SemanticsService.announce(screenTitle, TextDirection.ltr) inside a postFrameCallback in the screen's State.initState().

For focus management, attach a FocusNode to the primary action widget and call focusNode.requestFocus() in a postFrameCallback after state changes that land on an actionable state. Pay particular attention to the Personnummer bottom sheet — it opens over an existing screen so its focus must be trapped within the sheet (use FocusScope). The Blindeforbundet requirement for 'warning on reading sensitive fields' is addressed here by adding Semantics(hint: 'This field contains sensitive personal information') to the masked personnummer display.

Testing Requirements

Widget tests using flutter_test: use tester.pumpAndSettle() after BLoC state emission, then query SemanticsNode tree with tester.getSemantics() to verify labels, hints, and liveRegion flags are set correctly. Test that the primary action widget has autofocus: true or that FocusNode.hasFocus returns true after pump. Test that error state widgets include Semantics(liveRegion: true). Manual testing checklist: enable VoiceOver on iOS Simulator (Cmd+F5), navigate each screen and verify all announcements are spoken correctly; repeat with TalkBack on Android Emulator (triple-tap volume).

Document manual test results in the sign-off checklist produced in task-013.

Dependencies (5)
Add fallback logic to the Biometric Authentication Screen for devices where biometrics are unavailable or not enrolled. When biometrics are unsupported, display a clear explanation banner and route the user to BankID or Vipps re-authentication. When biometrics are available but not enrolled, show a prompt encouraging setup with a deep-link to device settings. Handle biometric lockout after multiple failures gracefully. epic-bankid-vipps-login-ui-task-008 Apply full WCAG 2.2 AA accessibility to the Authentication Method Selector Screen: correct semantic labels on all method cards and buttons, minimum 44x44 dp touch targets, 4.5:1 contrast ratio for all text against background, and logical focus order for keyboard and switch-access users. Add VoiceOver and TalkBack announcements when the screen loads so screen reader users are immediately oriented. epic-bankid-vipps-login-ui-task-002 Handle the OAuth deep-link callback on the Vipps Authentication Screen: parse the incoming URL from DeepLinkHandler, extract the authorization code, pass it to VippsAuthService, and navigate forward on success. Implement comprehensive error handling for user cancellation, network failures, and Vipps-side errors, displaying user-friendly plain-language error messages with a retry action. epic-bankid-vipps-login-ui-task-004 Implement the BankID deep-link and WebView callback handling on the BankID Authentication Screen: detect successful authentication responses, pass tokens to BankIDAuthService, and trigger navigation to the Personnummer Confirmation Widget. Handle all documented BankID error codes with specific, actionable user-facing messages. Include a Cancel action that cleanly aborts the BankID session and returns to the method selector. epic-bankid-vipps-login-ui-task-006 Add a privacy policy link and GDPR data-sharing disclosure to the Personnummer Confirmation Widget. The disclosure must clearly state which data is shared, with whom (the partner organization), and for what purpose. The privacy policy must open in an in-app browser. The acknowledgment action must be disabled until the user has either scrolled to the bottom of the disclosure text or explicitly tapped a 'Read disclosure' toggle. epic-bankid-vipps-login-ui-task-010
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.