alpa-components.css (2,539 lines) + Components/**/*.razor vs. the CLAUDE.md CSS rules & WCAG AA:focus-visible, add C# state, height→min-height that
doesn't reflow at base size) or GATED (visual/contrast/sizing change needing design
alignment — folded to the relevant DQ). Rules: CLAUDE.md "Blazor / CSS class rules";
WCAG 2.2 AA. Ties: DQ-18 (nav contrast) · DQ-21 (text scaling).
@media overrides); no real silent-override bug exists. The genuine hygiene items are
inline styles (concentrated in the two skeleton components) and one un-wired interactive control (Remember-Me).
Accessibility has one High-severity systemic gap: only 3 :focus rules in 2,539
lines (all on text inputs) — no visible keyboard focus on buttons, nav items, pills, tabs, toggles or cards.
Counts: hygiene 4 findings · a11y 6 findings · 6 Safe / 4 Gated.
:focus-visible utility this row proposed is in
alpa-components.css (L7768). A follow-on defect it exposed — Main.razor's
FocusOnNavigate leaving WebKit's native focus ring visible on the page heading after every
route change, because [tabindex="-1"] targets were excluded from the new utility by design but
nothing suppressed the browser default — is fixed alongside it (alpa-components.css L7789,
PR 2220).transform: scale() region. Covers 36 of 51 routable pages (every page rendering
<AlpaScreen>); the 15 uncovered pages keep unscoped native pinch, unchanged. See
D65 — Content-Only Pinch Zoom.FocusOnNavigate Selector="h1" (a11y: announces the page heading to AT after navigation) was
already in place before this audit and is unrelated to any of B1–B6; the only issue was the stray visible
ring above, now fixed. No further screen-reader-specific work is planned — any future
change here is scoped to what does not alter the experience for a sighted/mouse/touch member;
this is a defect fix, not the start of a screen-reader feature track.aria-* attributes and 13
distinct role values are in use across Components/**/*.razor, all 21
<img> have alt, and the custom-interactive <div>s already
follow the role="button" tabindex="0" pattern this row asks for. No further action identified;
still no automated a11y test coverage (unit or UI test) to catch a regression here.Still open, unchanged: B2 (nav-active contrast, gated on DQ-18) and B4 (sub-minimum tap targets, gated on design sizing).
| Finding | Location | Sev | Rule | Proposed fix | Class |
|---|---|---|---|---|---|
Static inline style= — ~26 in the skeleton components (widths, gaps, fixed heights) + 2 in ActionButton (display:block;width:100%;height:100%) |
Library/HomeSkeleton.razor, Library/ComponentSkeleton.razor, Library/ActionButton.razor:21,25 |
Med | No inline style= |
Move to named classes / a small set of skeleton width-utilities in the Utility block. Keep only genuinely dynamic @-bound styles. |
SAFE |
Legit dynamic inline styles (not violations) — --alpa-grid-cols:@Vm.Columns, width:@PercentClamped%, width:@MaxDPPercent%, skeleton width:@(45+i*7)% |
GridContainer:6, CumulativeBar:18, DutyPeriodCard:29, HomeSkeleton:45 | — | (allowed — value is data-bound) | No action. Bound values legitimately belong inline (or as a CSS custom property, already done for grid-cols). | OK |
Duplicate selectors — NO real bug. uniq -d flags were descendant selectors (.alpa-nav-item--active .alpa-nav-label vs … .alpa-nav-icon), grouped-then-specific pseudo blocks (::before,::after then ::after), and reduced-motion overrides. |
.alpa-sk/.alpa-home-content/.alpa-reveal dupes are inside @media (prefers-reduced-motion) (L513); .alpa-flight-card-line::after L1639/1650 is grouped+specific |
Info | No duplicate silent-override | None — verified clean. (Documented so the next audit doesn't re-flag the false positives.) | OK |
Interactive control without C# state — Remember-Me checkbox is a bare <input type="checkbox">, no @bind/@onclick/backing field |
Pages/LoginPage.razor:63 |
Med | Interactive controls must have state | Add a bool backing field + @bind (persist via SettingsService.RememberMe). Part of the login-scaffold wiring (see login data-gap §4). Note: the --active classes in KcmHomePage/MecHotelsPage are data-driven display states (e.g. "ACTIVE" airport badge), not tap-toggles — no violation. |
SAFE |
| Finding | Location | Sev | Criterion | Proposed fix | Class |
|---|---|---|---|---|---|
No visible keyboard focus on most controls — only 3 :focus rules in 2,539 lines, all on text inputs. Buttons, bottom-nav items, top-nav buttons, pills, tab-group tabs, toggles, action buttons and cards have no focus style. |
alpa-components.css — only L618 (login-input), L1447 (jsff-input), L2243 (ftdt-input) |
High | 2.4.7 Focus Visible; 2.1.1 Keyboard | Add a shared :focus-visible outline utility applied to all interactive controls. :focus-visible shows only on keyboard focus — no change to mouse/touch appearance, so visually safe. |
SAFE |
Bottom-nav active contrast — active label/icon use var(--alpa-blue) #007bc2 on navy #05273e ≈ 3.3:1 (fails AA for the small label). Design base is white label + light-blue #c7dff6 icon (passes). |
.alpa-nav-item--active L1513–1518 |
High | 1.4.3 Contrast | Adopt the design's light-blue base + a compliant active treatment. Gated on the active-state decision — DQ-18. | GATED |
Fixed heights block text reflow — 36 height:NNpx rules; the text-bearing ones clip when OS text scales (WebView ignores Dynamic Type entirely). |
e.g. cards/rows/labels at L192,344,350,573,860,1055,1338,1361 (icons/bars/dividers excluded) | Med-High | 1.4.4 Resize Text; 1.4.10 Reflow | Safe: swap fixed height→min-height on text containers (no base-size reflow). Gated: the WebView Dynamic-Type scale-bridge — DQ-21. |
SAFE / GATED |
Sub-minimum tap targets — several interactive controls below the 24×24px AA floor (and 44×44 comfort): top-nav button 40px, --button-sm/inputs 36px, and smaller pills/rows (32/28/26/22/20px). |
e.g. .alpa-top-nav-btn L1121 (40), inputs L1457/2128 (36), L271 (32), L741 (28) |
Med | 2.5.8 Target Size (Minimum) | Ensure interactive controls meet ≥24×24 (prefer 44×44) via padding/min-size, or an invisible hit-area. Sizing is visual → confirm against design. | GATED |
Icon-only controls — label verification — 66 aria attrs + 13 roles present and all 22 <img> have alt (good baseline). Spot-check icon-only buttons (e.g. top-nav SVG buttons) for an aria-label, and custom interactive <div>s for role/tabindex. |
.alpa-top-nav-btn (svg-only) + any tappable non-button element |
Low-Med | 4.1.2 Name, Role, Value; 1.1.1 | Add aria-label to icon-only buttons lacking a text node; ensure custom-interactive elements expose role + keyboard. |
SAFE |
Reduced-motion respected (positive) — a @media (prefers-reduced-motion: reduce) block (L513) neutralizes the skeleton/reveal animations. |
alpa-components.css L513 |
Info | 2.3.3 Animation from Interactions | None — keep. Extend the block if new animations are added. | OK |
:focus-visible utility, plus the stray-focus-ring
defect it exposed (B1) · height→min-height on text containers and the WebView
text-scale bridge (B3/DQ-21, AB#2391) · content-only pinch zoom as the accessibility backstop (D65, AB#2177) ·
Remember-Me C# state · inline-style→class in the skeletons · aria-label baseline on icon-only
buttons (B5).UnitTest/UITests.iOS/UITests.Android today) — nothing here currently catches a focus-ring or aria regression before a human notices it.Audited 2026-07-01 against scaffold branch feat/AB1821-login-scaffold-01 · alpa-components.css @ 2,539 lines · UI Refresh Epic AB#1821. Capture-only — no live values changed. Status refreshed 2026-08-28 against imp/blazor-hybrid — see the callout above.