Before submitting
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:
- 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.
- 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
Before submitting
Affected area
Developer experience / tooling
Layer
Frontend (apps/app), Infrastructure / CI
Problem
The a11y suites (e2e
checkAccessibility+ componentcheckContrast) 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 /
checkContrastnever sees today:Menuitems use_hover/_focusbackground tokens, a prime dark-mode contrast riskcypress-plugin-tabis already imported but unused for a11y)Proposed solution
Add tests that drive each state and then scan, in both modes:
checkAccessibility/checkContrast. These run in both modes automatically via the existing color-mode CI matrix.basicTests.cy.ts'scolorModesloop, after landing on a page: open a Share/Delete/New menu, focus a control (viacypress-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
upgrade-doenetml-dark-mode), which added the a11y audit this issue is drawn from.