A11y: keyboard focus moves on Tab but has no visible focus indicator (WCAG 2.4.7 fail) #97

Closed
opened 2026-04-30 03:56:26 +00:00 by Brouie · 9 comments
Brouie commented 2026-04-30 03:56:26 +00:00 (Migrated from gitlab.com)

Summary

Tab navigation works — focus DOES move between interactive elements as the user presses Tab — but there is no visible focus indicator on most/all interactive elements. Keyboard-only users cannot see where their focus currently sits, making the app non-navigable by keyboard despite focus actually being routable.

This is a WCAG 2.4.7 (Focus Visible, Level AA) violation.

Repro

  1. Stack 282eb62 up, Vite on 5173
  2. Open /timecurve in Chrome
  3. Click on the page body to put focus in the document (not URL bar)
  4. Press Tab repeatedly to advance focus
  5. Watch for any visual indicator (outline, glow, color change) on the currently-focused element

Observed

  • No visible outline, glow, or styling change appears on focused elements as Tab advances
  • Verified focus IS moving via document.activeElement polling in console — different elements gain focus on each Tab keystroke
  • Click-to-focus works (e.g., clicking BUY CHARM puts focus on it) but Tab-to-focus is invisible

Why this matters

  • WCAG 2.4.7 Level AA compliance is part of standard frontend ship criteria for production launches
  • Keyboard users (mouse-impaired users, power users) cannot navigate the app
  • Screen reader users may also lose orientation since visual focus is the canonical "where am I" cue
  • Common audit failure flagged by automated tools (Lighthouse a11y, axe DevTools, WAVE)

Suggested fix

Add :focus-visible styles to interactive elements. Standard pattern:

button:focus-visible,
a:focus-visible,
[role="button"]:focus-visible,
input:focus-visible,
select:focus-visible {
  outline: 2px solid var(--yo-focus-color, #...);
  outline-offset: 2px;
}

Use :focus-visible (not :focus) so the outline only shows on keyboard nav, not mouse click. The framework's CSS reset may be stripping default browser focus rings without replacing them — that's the most common cause.

Severity

MEDIUM. Not a launch-blocker but is an a11y compliance gap that will fail standard audits and excludes a real user segment. Cheap to fix (one CSS rule).

Scope note

This filing covers the focus-visible portion of F-15 only. Full a11y audit (semantic markup, ARIA labels, screen reader announcements, color contrast, etc.) deferred to post-TGE.

cc @PlasticDigits

## Summary Tab navigation works — focus DOES move between interactive elements as the user presses Tab — but **there is no visible focus indicator** on most/all interactive elements. Keyboard-only users cannot see where their focus currently sits, making the app non-navigable by keyboard despite focus actually being routable. This is a **WCAG 2.4.7 (Focus Visible, Level AA)** violation. ## Repro 1. Stack `282eb62` up, Vite on 5173 2. Open `/timecurve` in Chrome 3. Click on the page body to put focus in the document (not URL bar) 4. Press Tab repeatedly to advance focus 5. Watch for any visual indicator (outline, glow, color change) on the currently-focused element ## Observed - No visible outline, glow, or styling change appears on focused elements as Tab advances - Verified focus IS moving via `document.activeElement` polling in console — different elements gain focus on each Tab keystroke - Click-to-focus works (e.g., clicking BUY CHARM puts focus on it) but Tab-to-focus is invisible ## Why this matters - WCAG 2.4.7 Level AA compliance is part of standard frontend ship criteria for production launches - Keyboard users (mouse-impaired users, power users) cannot navigate the app - Screen reader users may also lose orientation since visual focus is the canonical "where am I" cue - Common audit failure flagged by automated tools (Lighthouse a11y, axe DevTools, WAVE) ## Suggested fix Add `:focus-visible` styles to interactive elements. Standard pattern: ```css button:focus-visible, a:focus-visible, [role="button"]:focus-visible, input:focus-visible, select:focus-visible { outline: 2px solid var(--yo-focus-color, #...); outline-offset: 2px; } ``` Use `:focus-visible` (not `:focus`) so the outline only shows on keyboard nav, not mouse click. The framework's CSS reset may be stripping default browser focus rings without replacing them — that's the most common cause. ## Severity **MEDIUM.** Not a launch-blocker but is an a11y compliance gap that will fail standard audits and excludes a real user segment. Cheap to fix (one CSS rule). ## Scope note This filing covers the focus-visible portion of F-15 only. Full a11y audit (semantic markup, ARIA labels, screen reader announcements, color contrast, etc.) deferred to post-TGE. cc @PlasticDigits
PlasticDigits commented 2026-04-30 04:05:02 +00:00 (Migrated from gitlab.com)

mentioned in commit 0a7679503d

mentioned in commit 0a7679503d0ad98f154d94532dbd9b2251616daa
PlasticDigits commented 2026-04-30 04:05:33 +00:00 (Migrated from gitlab.com)

Fix shipped on ,main (commit 0a76795)

What changed

  • Root cause: RainbowKit’s stylesheet applies [data-rk] .iekbcc5 { outline: none } (specificity 0,2,0), which beats our previous global button:focus-visible rule (0,1,1), so Tab focus was invisible inside wallet chrome and could look broken elsewhere.
  • Fix: In frontend/src/index.css: added --yo-focus-ring on :root; expanded :focus-visible selectors ([role="button"], summary, focusable [tabindex]:not([tabindex="-1"])); duplicated the same selector list under [data-rk] so RainbowKit cannot strip keyboard focus rings.
  • Docs / agent UX: docs/frontend/design.md, wallet-connection.md, timecurve-views.md, docs/testing/invariants-and-business-logic.md (table + §97), docs/testing/strategy.md; play skill skills/verify-yo-focus-visible-a11y/SKILL.md; contributor guardrails item 30.

Checklist for @brouie (please verify — issue stays open until you sign off)

  • /timecurve: Tab through sub-nav, pay toggles, Buy CHARM, slider — visible gold (or themed) ring on each stop; mouse-click primary CTA does not leave a spurious permanent ring (:focus-visible semantics).
  • Open Connect wallet modal: Tab through options — visible focus on every interactive control (RainbowKit [data-rk] path).
  • Optional: DevTools console — document.activeElement matches the control showing the ring.
  • Spot-check another route (e.g. /referrals) for Tab rings on links/buttons.

Refs: invariants — keyboard focus visible · play skill

cc @brouie

## Fix shipped on `,main` (commit `0a76795`) ### What changed - **Root cause:** RainbowKit’s stylesheet applies `[data-rk] .iekbcc5 { outline: none }` (specificity **0,2,0**), which **beats** our previous global `button:focus-visible` rule (**0,1,1**), so Tab focus was **invisible** inside wallet chrome and could look broken elsewhere. - **Fix:** In `frontend/src/index.css`: added `--yo-focus-ring` on `:root`; expanded **:focus-visible** selectors (`[role="button"]`, `summary`, focusable `[tabindex]:not([tabindex="-1"])`); **duplicated the same selector list under `[data-rk]`** so RainbowKit cannot strip keyboard focus rings. - **Docs / agent UX:** `docs/frontend/design.md`, `wallet-connection.md`, `timecurve-views.md`, `docs/testing/invariants-and-business-logic.md` (table + §97), `docs/testing/strategy.md`; play skill `skills/verify-yo-focus-visible-a11y/SKILL.md`; contributor guardrails item **30**. ### Checklist for @brouie (please verify — issue stays open until you sign off) - [ ] `/timecurve`: Tab through sub-nav, pay toggles, **Buy CHARM**, slider — **visible** gold (or themed) ring on each stop; mouse-click primary CTA does **not** leave a spurious permanent ring (`:focus-visible` semantics). - [ ] Open **Connect wallet** modal: Tab through options — **visible** focus on every interactive control (RainbowKit `[data-rk]` path). - [ ] Optional: DevTools console — `document.activeElement` matches the control showing the ring. - [ ] Spot-check another route (e.g. `/referrals`) for Tab rings on links/buttons. Refs: [invariants — keyboard focus visible](docs/testing/invariants-and-business-logic.md#keyboard-focus-visible-wcag-247-gitlab-97) · [play skill](skills/verify-yo-focus-visible-a11y/SKILL.md) cc @brouie
PlasticDigits commented 2026-04-30 04:05:47 +00:00 (Migrated from gitlab.com)

Correction on wording above: fix is on main at commit 0a76795 (GitLab may have mangled backticks in the previous note).

Correction on wording above: fix is on **main** at commit **0a76795** (GitLab may have mangled backticks in the previous note).
Brouie commented 2026-04-30 04:21:08 +00:00 (Migrated from gitlab.com)

mentioned in issue #38

mentioned in issue #38
Brouie commented 2026-04-30 05:06:26 +00:00 (Migrated from gitlab.com)

Verified 0a76795 against all 4 SKILL.md acceptance items on the SKIP+KUMBAYA+NO_COOLDOWN stack today (aea2072 HEAD with Vite restarted to pick up the new index.css). Closing.

Item 1 — /timecurve Tab focus rings: PASS

Tabbed through /timecurve after click-on-page-body to put focus in document. Visible gold (themed --yo-focus-ring) ring appeared at each Tab stop:

  • Header nav links (TimeCurve / Rabbit Treasury / Collection / Referrals / Kumbaya / Sir)
  • Wallet pill in header
  • Sub-nav (Simple / Arena / Protocol)
  • Pay-mode toggles (CL8Y / ETH / USDM)
  • BUY CHARM CTA
  • Range slider

Mouse-click on BUY CHARM with :focus-visible semantics: confirmed the click does not produce a problematic permanent-ring artifact (the brief ring observation is consistent with :focus-visible behavior under MetaMask click → wallet popup focus → return focus, not a CSS regression).

Item 2 — Connect Wallet modal: PASS

Opened the wallet pill / Connect Wallet flow. Tabbed through wallet option list inside the RainbowKit [data-rk] modal. Each focused control showed the same family of visible focus styling as the rest of the app — confirming the duplicated :focus-visible selectors under [data-rk] correctly defeat RainbowKit's outline: none (specificity 0,2,0) override.

Item 3 — Contrast sanity: PASS

Gold focus ring stays clearly visible across:

  • Light cream panels (Buy CHARM card, vesting card backgrounds)
  • Dark green page chrome (header, body background)
  • Modal overlay (Connect Wallet popup against backdrop)

No contrast issues observed in either focus state vs adjacent backgrounds.

Item 4 — Spot-check /referrals: PASS

Navigated to /referrals and Tab-walked through the new sections (Connected wallet, Register a code form input + button, Referrer leaderboard, footer links). Ring visible at every Tab stop. Same gold themed ring as /timecurve, consistent app-wide.

Closure recommendation

:focus-visible rings are now visible across the whole app and inside RainbowKit-scoped wallet UI. WCAG 2.4.7 (Focus Visible, Level AA) satisfied for the surfaces I exercised. The CSS approach (centralized --yo-focus-ring token + selector list duplicated under [data-rk]) is robust against future RainbowKit version bumps as long as the [data-rk] scoping convention stays stable.

Closing #97.

cc @PlasticDigits

Verified `0a76795` against all 4 SKILL.md acceptance items on the SKIP+KUMBAYA+NO_COOLDOWN stack today (`aea2072` HEAD with Vite restarted to pick up the new index.css). Closing. ## Item 1 — `/timecurve` Tab focus rings: PASS Tabbed through `/timecurve` after click-on-page-body to put focus in document. Visible gold (themed `--yo-focus-ring`) ring appeared at each Tab stop: - Header nav links (TimeCurve / Rabbit Treasury / Collection / Referrals / Kumbaya / Sir) - Wallet pill in header - Sub-nav (Simple / Arena / Protocol) - Pay-mode toggles (CL8Y / ETH / USDM) - BUY CHARM CTA - Range slider Mouse-click on BUY CHARM with `:focus-visible` semantics: confirmed the click does not produce a problematic permanent-ring artifact (the brief ring observation is consistent with `:focus-visible` behavior under MetaMask click → wallet popup focus → return focus, not a CSS regression). ## Item 2 — Connect Wallet modal: PASS Opened the wallet pill / Connect Wallet flow. Tabbed through wallet option list inside the RainbowKit `[data-rk]` modal. Each focused control showed the same family of visible focus styling as the rest of the app — confirming the duplicated `:focus-visible` selectors under `[data-rk]` correctly defeat RainbowKit's `outline: none` (specificity 0,2,0) override. ## Item 3 — Contrast sanity: PASS Gold focus ring stays clearly visible across: - Light cream panels (Buy CHARM card, vesting card backgrounds) - Dark green page chrome (header, body background) - Modal overlay (Connect Wallet popup against backdrop) No contrast issues observed in either focus state vs adjacent backgrounds. ## Item 4 — Spot-check `/referrals`: PASS Navigated to `/referrals` and Tab-walked through the new sections (Connected wallet, Register a code form input + button, Referrer leaderboard, footer links). Ring visible at every Tab stop. Same gold themed ring as `/timecurve`, consistent app-wide. ## Closure recommendation `:focus-visible` rings are now visible across the whole app and inside RainbowKit-scoped wallet UI. WCAG 2.4.7 (Focus Visible, Level AA) satisfied for the surfaces I exercised. The CSS approach (centralized `--yo-focus-ring` token + selector list duplicated under `[data-rk]`) is robust against future RainbowKit version bumps as long as the `[data-rk]` scoping convention stays stable. Closing #97. cc @PlasticDigits
Brouie (Migrated from gitlab.com) closed this issue 2026-04-30 05:06:28 +00:00
PlasticDigits commented 2026-04-30 05:25:10 +00:00 (Migrated from gitlab.com)

mentioned in issue #100

mentioned in issue #100
PlasticDigits commented 2026-05-21 09:32:21 +00:00 (Migrated from gitlab.com)

mentioned in issue #232

mentioned in issue #232
PlasticDigits commented 2026-05-27 07:07:47 +00:00 (Migrated from gitlab.com)

mentioned in issue #258

mentioned in issue #258
PlasticDigits commented 2026-06-03 13:37:20 +00:00 (Migrated from gitlab.com)

mentioned in merge request !414

mentioned in merge request !414
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
code/yieldomega#97
No description provided.