critical priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

When organization.isSelected is true, the card displays a visually distinct border or background tint
The selected border color or tint achieves a minimum 3:1 contrast ratio against the card's unselected background (WCAG 2.2 AA non-text requirement)
When organization.isSelected is false, the card displays the default unselected appearance with no selected indicators
Selected and unselected colors are sourced from design tokens — no hardcoded hex values
A checkmark icon or equivalent indicator is shown inside the card when isSelected is true, providing a redundant non-color signal (color must not be the sole differentiator per WCAG 1.4.1)
Transitioning between selected and unselected (via widget rebuild with updated Organization) renders correctly without layout jumps
Widget compiles and renders correctly in both selected and unselected states in widget tests
Visual difference is confirmed via a golden test or manual review screenshot

Technical Requirements

frameworks
Flutter (StatelessWidget)
Design token system (selected color, border tokens)
AnimatedContainer (optional — for smooth state transition)
data models
Organization (isSelected field)
performance requirements
If AnimatedContainer is used, duration must be ≤200ms to feel responsive without delaying user action
No setState or internal state — isSelected is driven entirely by the Organization.isSelected field passed from parent
ui components
OrgCard border/background state layer
Icon widget (checkmark or tick for redundant indicator)

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

The most robust approach is a Container with a BoxDecoration that conditionally sets a border (Border.all with selectedBorderColor token, width 2dp) and a subtle background tint (selectedBackgroundColor token with low opacity ~10–15%). This avoids layout shifts since a transparent 2dp border should be present in the unselected state too (borderColor: Colors.transparent) to prevent card size changes on selection toggle. Use an Icons.check_circle or Icons.check icon in the top-right corner for the non-color signal, shown conditionally via Visibility or if-else in the widget tree. Reference the existing design token system (AppColors.selectedBorder or equivalent) — do not introduce new color constants without confirming they belong in the token system.

Testing Requirements

Widget tests: (1) pump OrgCard with isSelected=true and assert that the checkmark icon (or equivalent finder) is present in the widget tree, (2) pump OrgCard with isSelected=false and assert the checkmark icon is absent, (3) pump with isSelected=false, then rebuild with isSelected=true via tester.pumpWidget(), and assert the widget updates without throwing. For contrast validation, run a manual accessibility check using the Flutter Inspector's accessibility panel or calculate contrast ratio offline using the token color values. Add a golden test if the project has a golden testing setup.

Component
Organization Card Widget
ui low
Epic Risks (3)
high impact medium prob technical

Flutter's Semantics system requires explicit configuration for custom card widgets — the default widget tree semantics may merge child nodes incorrectly, causing VoiceOver to announce partial or garbled text for each OrgCard. This is a critical failure for Blindeforbundet and violates the contractual WCAG 2.2 AA requirement.

Mitigation & Contingency

Mitigation: Wrap each OrgCard in a Semantics widget with an explicit label combining organization name and role label, set button role, and excludeSemantics: true on all child widgets to prevent double-announcement. Test on a physical iOS device with VoiceOver enabled before marking the epic complete.

Contingency: If automated semantics are insufficient, implement a dedicated AccessibilityLabel builder in the OrgCard that constructs the announcement string from OrgLabelsProvider output and passes it directly to the Semantics label field.

medium impact medium prob technical

Organization logos must load before the screen is considered ready. If assets are fetched from a remote URL and network latency is high (a realistic scenario for rural NHF users), the screen may render with broken image placeholders, degrading the first-impression quality and potentially confusing screen reader users whose VoiceOver announcements would reference an empty image.

Mitigation & Contingency

Mitigation: Cache organization logos locally after first load using the org-branding-cache pattern. For the initial load, render a styled placeholder with the organization's initials and primary color while the logo loads asynchronously. Ensure the Semantics label is derived from the organization name, not the image asset, so screen reader announcements are never dependent on image load state.

Contingency: If caching implementation is out of scope for this epic, ship with initials-based placeholders as permanent fallbacks and defer logo loading to the org-branding-cache epic. The screen remains fully functional and accessible without logos.

medium impact low prob scope

The user story specifies that single-organization users bypass the selection screen and are routed directly into the app. If the bypass logic is implemented in the UI layer rather than in OrgSelectionService, it creates a tight coupling that breaks if the routing logic is ever reused or tested in isolation. It also creates a potential timing issue where the screen briefly renders before the bypass fires.

Mitigation & Contingency

Mitigation: Implement the single-org bypass entirely in OrgSelectionService.getPersistedSelection() or as a route guard — the screen should never be pushed onto the navigation stack for single-org users. The OrgSelectionScreen widget itself has no knowledge of this bypass.

Contingency: If the route guard approach is deferred to the routing epic, add a local guard in the OrgSelectionScreen's initState that calls OrgSelectionService, auto-selects if count is 1, and navigates forward before the first frame is painted, using a loading indicator to prevent the flash.