Skip to content

[Feature]: contrast coverage for transient/interactive UI states (both color modes) #3014

Description

@dqnykamp

Before submitting

  • I searched existing issues and did not find a duplicate request.

Affected area

Developer experience / tooling

Layer

Frontend (apps/app), Infrastructure / CI

Problem

The a11y suites (e2e checkAccessibility + component checkContrast) scan mostly default / initial render. Transient and interactive states are never scanned over a live page in either color mode, so the dark-mode-only color branches on those states are unverified. This is exactly where hardcoded light colors survive a static review.

States that axe / checkContrast never sees today:

  • Open modals / dialogs driven from a real page (they're only checked as isolated component mounts with mocked data — never composited over a live, dark page + backdrop)
  • Open dropdown / select menus on a real route — Chakra Menu items use _hover / _focus background tokens, a prime dark-mode contrast risk
  • Keyboard focus-visible rings — no test drives focus; focus-ring-vs-background contrast is unmeasured (cypress-plugin-tab is already imported but unused for a11y)
  • Form validation / error states on routed pages (error text, often red on dark)
  • Loading spinners / skeletons (loaders resolve before assertions fire)
  • Toasts / alerts that appear after an action, over the live page
  • Expanded accordions / non-first tab panels
  • Drawers / side panels opened over a live route
  • Disabled controls (disabled text often drops below AA; Chakra's disabled opacity differs by mode)

Proposed solution

Add tests that drive each state and then scan, in both modes:

  1. Component tier — extend existing widget/page specs to open the menu / modal / drawer, trigger the error / disabled / loading state, or switch to a non-first tab, then checkAccessibility / checkContrast. These run in both modes automatically via the existing color-mode CI matrix.
  2. e2e tier — inside basicTests.cy.ts's colorModes loop, after landing on a page: open a Share/Delete/New menu, focus a control (via cypress-plugin-tab), submit an invalid form, or trigger a toast, then scan.

Prioritize Menu hover/focus and focus-visible rings first — highest dark-mode risk and currently zero coverage. (Animations are already frozen in the harness, so axe won't sample transitional frames.)

Alternatives considered

Relying on the isolated component specs that already exist for these widgets — insufficient, because they render with mocked data on a default background and never exercise the state composited over a live page in dark mode, which is where the real regressions hide.

Additional context

Metadata

Metadata

Assignees

No one assigned

    Fields

    Priority

    None yet

    Effort

    None yet

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions