[a11y] Focus visibility audit — buttons/nav/wallet-rows have no visible focus indicator (WCAG 2.4.7) #144

Closed
opened 2026-05-07 06:36:59 +00:00 by Brouie · 18 comments
Brouie commented 2026-05-07 06:36:59 +00:00 (Migrated from gitlab.com)

@totdking — visual / a11y finding from source-level audit, assigning to you. broader pattern across the dapp where interactive elements lack visible focus indicators. extends DEX visual QA umbrella #133. relates to WCAG 2.4.7 (Focus Visible, Level AA).

Scope

Source audit found that inputs ship explicit focus rings, but most styled buttons and nav links do NOT. Additionally, :focus-visible is not used anywhere in the codebase (zero hits in index.css) — all explicit focus rules use :focus, so mouse users see the ring after click as well as keyboard users. Industry standard is :focus-visible so the ring renders only on keyboard focus.

Each of these has :hover rules but NO :focus / :focus-visible rule. Browser default applies (often a thin/dotted ring or none, depending on browser preflight reset).

Site Class
frontend-dapp/src/index.css:157-166 .btn-primary (only :hover at line 168)
frontend-dapp/src/index.css:173-188 .btn-muted (same pattern)
frontend-dapp/src/index.css:190-202 .btn-cta (same pattern)
frontend-dapp/src/index.css:630-669 .app-nav-link, .app-more-trigger, .app-mobile-link, .app-mobile-more, .app-menu-link, .app-footer-theme-button, .network-badge, .wallet-trigger (all :hover only at :658-665)
frontend-dapp/src/index.css:929-949 .wallet-option-card — connect modal wallet rows (only :hover)
frontend-dapp/src/index.css:456-476 .tab-neo* family — verify; similarly hover-only

HIGH — input with focus:outline-none and no substitute

frontend-dapp/src/pages/SwapPage.tsx:797:

className="w-full text-[1.75rem] sm:text-2xl font-medium bg-transparent focus:outline-none"

The primary You-Pay amount input strips the browser focus ring without supplying a replacement. Keyboard-only users get no visible focus indicator on the page's MOST IMPORTANT field. This is also the same input flagged in DEX #143 (form labels) for missing label association — same input has two a11y gaps.

POLISH — :focus used where :focus-visible would be cleaner

Currently applies the focus ring on every focus event including mouse click. Switch to :focus-visible so the ring only renders on keyboard focus:

Site Class
index.css:230-236 .input-neo:focus
index.css:250-256 .select-neo:focus
index.css:281-287 .token-select-trigger:focus

How to verify on local stack

  1. Start the local DEX stack
  2. Open the dapp at http://127.0.0.1:3000
  3. Keyboard test: click somewhere in the page body to put focus in the document, then press Tab repeatedly to advance focus through the page
  4. Watch for any visual indicator (outline, glow, color change) on the currently-focused element
  5. Buttons (Connect Wallet, Swap, Pool, Limits, Trade nav, etc.), nav links, wallet-option-card in connect modal — should all show NO visible focus indicator (browser default may be invisible)
  6. Inputs (search, slippage, price, amount fields) — should show the existing .input-neo:focus ring (works today)
  7. Verify focus IS moving via document.activeElement polling in console — different elements gain focus on each Tab keystroke even though no visual indicator appears

This is a WCAG 2.4.7 violation — focus DOES move (so navigation works) but is NOT visible (so users can't see where they are).

Reference patterns (correct sites — use as the model when fixing)

.input-neo:focus at index.css:230-236 — the existing focus styling on form inputs is the right shape. Apply the same ring (or visually-similar) to .btn-*, .app-nav-link*, .wallet-trigger, .wallet-option-card, .tab-neo*.

Suggested fix shape

Add :focus-visible rules to the missing classes:

.btn-primary:focus-visible,
.btn-muted:focus-visible,
.btn-cta:focus-visible,
.app-nav-link:focus-visible,
.app-more-trigger:focus-visible,
.app-mobile-link:focus-visible,
.app-mobile-more:focus-visible,
.app-menu-link:focus-visible,
.wallet-trigger:focus-visible,
.wallet-option-card:focus-visible,
.tab-neo:focus-visible {
  outline: 2px solid var(--brand-focus);
  outline-offset: 2px;
}

(Use whatever visual rhythm matches .input-neo:focus's existing ring color/offset.)

For the SwapPage You-Pay input at SwapPage.tsx:797, drop focus:outline-none — let the input inherit a default focus ring, OR replace with a Tailwind focus-visible: utility that supplies a visible alternative.

For the existing :focus rules on .input-neo, .select-neo, .token-select-trigger, migrate to :focus-visible so mouse users don't see the ring after click (purely a polish, not a violation).

Severity

P2 — WCAG 2.4.7 (Focus Visible, Level AA) violation on most interactive elements. Keyboard-only users cannot see where their focus currently sits. Compounds with #143 (form labels) — both gaps affect the same You-Pay input plus the dapp's primary CTAs.

Cross-reference

  • DEX #133 — visual QA umbrella
  • DEX #143 — form-input label sweep (sister a11y issue, same You-Pay input affected)
  • YO #97 (closed) — same WCAG 2.4.7 violation surface in YieldOmega; was previously fixed there. Pattern parity worth preserving across both products.

cc @PlasticDigits

@totdking — visual / a11y finding from source-level audit, assigning to you. broader pattern across the dapp where interactive elements lack visible focus indicators. extends DEX visual QA umbrella #133. relates to WCAG 2.4.7 (Focus Visible, Level AA). ## Scope Source audit found that **inputs ship explicit focus rings, but most styled buttons and nav links do NOT.** Additionally, `:focus-visible` is not used anywhere in the codebase (zero hits in `index.css`) — all explicit focus rules use `:focus`, so mouse users see the ring after click as well as keyboard users. Industry standard is `:focus-visible` so the ring renders only on keyboard focus. ## HIGH — buttons/links missing explicit focus styling Each of these has `:hover` rules but NO `:focus` / `:focus-visible` rule. Browser default applies (often a thin/dotted ring or none, depending on browser preflight reset). | Site | Class | |---|---| | `frontend-dapp/src/index.css:157-166` | `.btn-primary` (only `:hover` at line 168) | | `frontend-dapp/src/index.css:173-188` | `.btn-muted` (same pattern) | | `frontend-dapp/src/index.css:190-202` | `.btn-cta` (same pattern) | | `frontend-dapp/src/index.css:630-669` | `.app-nav-link`, `.app-more-trigger`, `.app-mobile-link`, `.app-mobile-more`, `.app-menu-link`, `.app-footer-theme-button`, `.network-badge`, `.wallet-trigger` (all `:hover` only at `:658-665`) | | `frontend-dapp/src/index.css:929-949` | `.wallet-option-card` — connect modal wallet rows (only `:hover`) | | `frontend-dapp/src/index.css:456-476` | `.tab-neo*` family — verify; similarly hover-only | ## HIGH — input with focus:outline-none and no substitute `frontend-dapp/src/pages/SwapPage.tsx:797`: ```tsx className="w-full text-[1.75rem] sm:text-2xl font-medium bg-transparent focus:outline-none" ``` The primary You-Pay amount input strips the browser focus ring without supplying a replacement. Keyboard-only users get no visible focus indicator on the page's MOST IMPORTANT field. This is also the same input flagged in DEX #143 (form labels) for missing label association — same input has two a11y gaps. ## POLISH — `:focus` used where `:focus-visible` would be cleaner Currently applies the focus ring on every focus event including mouse click. Switch to `:focus-visible` so the ring only renders on keyboard focus: | Site | Class | |---|---| | `index.css:230-236` | `.input-neo:focus` | | `index.css:250-256` | `.select-neo:focus` | | `index.css:281-287` | `.token-select-trigger:focus` | ## How to verify on local stack 1. Start the local DEX stack 2. Open the dapp at http://127.0.0.1:3000 3. **Keyboard test**: click somewhere in the page body to put focus in the document, then press Tab repeatedly to advance focus through the page 4. Watch for any visual indicator (outline, glow, color change) on the currently-focused element 5. Buttons (Connect Wallet, Swap, Pool, Limits, Trade nav, etc.), nav links, wallet-option-card in connect modal — should all show NO visible focus indicator (browser default may be invisible) 6. Inputs (search, slippage, price, amount fields) — should show the existing `.input-neo:focus` ring (works today) 7. Verify focus IS moving via `document.activeElement` polling in console — different elements gain focus on each Tab keystroke even though no visual indicator appears This is a WCAG 2.4.7 violation — focus DOES move (so navigation works) but is NOT visible (so users can't see where they are). ## Reference patterns (correct sites — use as the model when fixing) `.input-neo:focus` at `index.css:230-236` — the existing focus styling on form inputs is the right shape. Apply the same ring (or visually-similar) to `.btn-*`, `.app-nav-link*`, `.wallet-trigger`, `.wallet-option-card`, `.tab-neo*`. ## Suggested fix shape Add `:focus-visible` rules to the missing classes: ```css .btn-primary:focus-visible, .btn-muted:focus-visible, .btn-cta:focus-visible, .app-nav-link:focus-visible, .app-more-trigger:focus-visible, .app-mobile-link:focus-visible, .app-mobile-more:focus-visible, .app-menu-link:focus-visible, .wallet-trigger:focus-visible, .wallet-option-card:focus-visible, .tab-neo:focus-visible { outline: 2px solid var(--brand-focus); outline-offset: 2px; } ``` (Use whatever visual rhythm matches `.input-neo:focus`'s existing ring color/offset.) For the SwapPage You-Pay input at `SwapPage.tsx:797`, drop `focus:outline-none` — let the input inherit a default focus ring, OR replace with a Tailwind `focus-visible:` utility that supplies a visible alternative. For the existing `:focus` rules on `.input-neo`, `.select-neo`, `.token-select-trigger`, migrate to `:focus-visible` so mouse users don't see the ring after click (purely a polish, not a violation). ## Severity P2 — WCAG 2.4.7 (Focus Visible, Level AA) violation on most interactive elements. Keyboard-only users cannot see where their focus currently sits. Compounds with #143 (form labels) — both gaps affect the same You-Pay input plus the dapp's primary CTAs. ## Cross-reference - DEX #133 — visual QA umbrella - DEX #143 — form-input label sweep (sister a11y issue, same You-Pay input affected) - YO #97 (closed) — same WCAG 2.4.7 violation surface in YieldOmega; was previously fixed there. Pattern parity worth preserving across both products. cc @PlasticDigits
Brouie commented 2026-05-07 06:36:59 +00:00 (Migrated from gitlab.com)

assigned to @totdking

assigned to @totdking
PlasticDigits commented 2026-05-09 05:01:52 +00:00 (Migrated from gitlab.com)

@Brouie Should not be assigned to totdking as this is a bugfix not a verification

@Brouie Should not be assigned to totdking as this is a bugfix not a verification
PlasticDigits commented 2026-05-09 05:01:54 +00:00 (Migrated from gitlab.com)

unassigned @totdking

unassigned @totdking
PlasticDigits commented 2026-05-09 05:02:57 +00:00 (Migrated from gitlab.com)

mentioned in issue #145

mentioned in issue #145
PlasticDigits commented 2026-05-09 05:04:24 +00:00 (Migrated from gitlab.com)

mentioned in issue #146

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

mentioned in commit 41dbd1f725

mentioned in commit 41dbd1f725f4ec04a0c4eb777c2bf9c3f8e3c69c
PlasticDigits commented 2026-05-09 05:10:03 +00:00 (Migrated from gitlab.com)

Implemented (merged to main)

WCAG 2.4.7 focus visibility:

  • frontend-dapp/src/index.css: :focus-visible rings aligned with .input-neo (`--focus-ring` / `color-mix`) for .btn-primary / .btn-muted / .btn-primary.btn-cta, shell nav (.app-nav-link, More/mobile/footer triggers, .network-badge), .wallet-trigger (+ .wallet-trigger-connected), active-nav stacking, .app-menu-link / .wallet-menu-item, .tab-neo*, .wallet-option-card.
  • Form controls: .input-neo, .select-neo, .token-select-trigger migrated from :focus → :focus-visible.
  • Swap You Pay input: class swap-io-amount-input; removed Tailwind focus:outline-none; ring via CSS beside .swap-io-stack.

Docs: docs/frontend.md § Keyboard focus visibility; agent playbook skills/AGENTS_FRONTEND_A11Y_FOCUS.md; cross-link from skills/AGENTS_FRONTEND_RESPONSIVE_HEADER.md.

Verification checklist (please confirm locally):

  1. Start the DEX stack and open the dapp (e.g. http://127.0.0.1:3000 or your Vite port).
  2. Click in the page, then Tab through: primary CTAs, header nav / More / wallet, bottom nav (mobile), Swap tabs, You Pay amount field, token triggers, connect-modal wallet-option-card rows.
  3. Confirm a visible ring (or equivalent) appears on each focused control without needing mouse hover.
  4. Mouse: after clicking a button/link, confirm no sticky focus ring where the UA uses :focus-visible semantics (rings should skew keyboard-forward).

Requested verification: @brouie — issue stays open until you sign off.

Refs: merge on main (GitLab origin/main). Related: #143 (labels / same input).

## Implemented (merged to `main`) WCAG **2.4.7** focus visibility: - **`frontend-dapp/src/index.css`**: `:focus-visible` rings aligned with `.input-neo` (\`--focus-ring\` / \`color-mix\`) for `.btn-primary` / `.btn-muted` / `.btn-primary.btn-cta`, shell nav (`.app-nav-link`, More/mobile/footer triggers, `.network-badge`), `.wallet-trigger` (+ `.wallet-trigger-connected`), active-nav stacking, `.app-menu-link` / `.wallet-menu-item`, `.tab-neo*`, `.wallet-option-card`. - **Form controls**: `.input-neo`, `.select-neo`, `.token-select-trigger` migrated from `:focus` → `:focus-visible`. - **Swap You Pay input**: class `swap-io-amount-input`; removed Tailwind `focus:outline-none`; ring via CSS beside `.swap-io-stack`. **Docs**: [`docs/frontend.md` § Keyboard focus visibility](docs/frontend.md#keyboard-focus-visible-wcag-247); agent playbook [`skills/AGENTS_FRONTEND_A11Y_FOCUS.md`](skills/AGENTS_FRONTEND_A11Y_FOCUS.md); cross-link from [`skills/AGENTS_FRONTEND_RESPONSIVE_HEADER.md`](skills/AGENTS_FRONTEND_RESPONSIVE_HEADER.md). **Verification checklist** (please confirm locally): 1. Start the DEX stack and open the dapp (e.g. http://127.0.0.1:3000 or your Vite port). 2. Click in the page, then **Tab** through: primary CTAs, header nav / More / wallet, bottom nav (mobile), Swap tabs, **You Pay** amount field, token triggers, connect-modal **wallet-option-card** rows. 3. Confirm a **visible ring** (or equivalent) appears on each focused control **without** needing mouse hover. 4. **Mouse**: after clicking a button/link, confirm **no** sticky focus ring where the UA uses `:focus-visible` semantics (rings should skew keyboard-forward). Requested verification: @brouie — issue stays **open** until you sign off. Refs: merge on `main` (GitLab `origin/main`). Related: #143 (labels / same input).
PlasticDigits commented 2026-05-27 02:58:28 +00:00 (Migrated from gitlab.com)

Verification complete (local QA + browser MCP)

Verified WCAG 2.4.7 focus visibility on main @ 7f37096 — no additional code changes required; implementation from the prior merge satisfies the issue.

Stack

  • LocalTerra via Docker Compose (RPC/LCD healthy)
  • Frontend dev server @ http://127.0.0.1:3000 (worktree clone, .env.local from QA)

Checklist (issue + docs/frontend.md § Keyboard focus visibility)

  1. Tab / keyboard focus rings — CDP :focus-visible forced-pseudo audit on live UI:
    • a.app-nav-link — 2px --focus-ring outer ring stacked with active nav shadow ✓
    • .wallet-option-card (Connect Wallet modal) — 2px ring ✓ (screenshot: Simulated Wallet row bordered)
    • .swap-io-amount-input — 2px ring ✓ (verified earlier in session before chain redeploy)
  2. Stylesheet coverage — 29 :focus-visible rules loaded; required selectors present (.btn-primary, .btn-muted, shell .app-*, .wallet-trigger, .tab-neo*, .token-select-trigger, .input-neo, .swap-io-amount-input, .wallet-option-card). No focus:outline-none in frontend.
  3. Mouse vs keyboard (polish) — after mouse-click on nav link, :focus-visible is false and no outer ring (keyboard-only emphasis) ✓
  4. Swap You Pay input — uses swap-io-amount-input class; Tailwind focus:outline-none removed ✓

Notes

  • .token-select-trigger:focus { outline: none; } remains intentionally — ring color applied via :focus-visible only (GitLab #181 footprint stability).
  • .select-neo:focus-visible exists in index.css; no live <select class="select-neo"> on Swap route today.

Closing as verified.

## Verification complete (local QA + browser MCP) Verified WCAG **2.4.7** focus visibility on `main` @ 7f37096 — no additional code changes required; implementation from the prior merge satisfies the issue. ### Stack - LocalTerra via Docker Compose (RPC/LCD healthy) - Frontend dev server @ http://127.0.0.1:3000 (worktree clone, `.env.local` from QA) ### Checklist (issue + `docs/frontend.md` § Keyboard focus visibility) 1. **Tab / keyboard focus rings** — CDP `:focus-visible` forced-pseudo audit on live UI: - `a.app-nav-link` — 2px `--focus-ring` outer ring stacked with active nav shadow ✓ - `.wallet-option-card` (Connect Wallet modal) — 2px ring ✓ (screenshot: Simulated Wallet row bordered) - `.swap-io-amount-input` — 2px ring ✓ (verified earlier in session before chain redeploy) 2. **Stylesheet coverage** — 29 `:focus-visible` rules loaded; required selectors present (`.btn-primary`, `.btn-muted`, shell `.app-*`, `.wallet-trigger`, `.tab-neo*`, `.token-select-trigger`, `.input-neo`, `.swap-io-amount-input`, `.wallet-option-card`). No `focus:outline-none` in frontend. 3. **Mouse vs keyboard (polish)** — after mouse-click on nav link, `:focus-visible` is false and no outer ring (keyboard-only emphasis) ✓ 4. **Swap You Pay input** — uses `swap-io-amount-input` class; Tailwind `focus:outline-none` removed ✓ ### Notes - `.token-select-trigger:focus { outline: none; }` remains intentionally — ring color applied via `:focus-visible` only (GitLab #181 footprint stability). - `.select-neo:focus-visible` exists in `index.css`; no live `<select class="select-neo">` on Swap route today. Closing as verified.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-05-27 02:58:30 +00:00
PlasticDigits commented 2026-05-29 03:16:36 +00:00 (Migrated from gitlab.com)

mentioned in issue #214

mentioned in issue #214
PlasticDigits commented 2026-06-07 12:14:16 +00:00 (Migrated from gitlab.com)

mentioned in issue #337

mentioned in issue #337
PlasticDigits commented 2026-08-15 21:45:48 +00:00 (Migrated from gitlab.com)

mentioned in issue #528

mentioned in issue #528
PlasticDigits commented 2026-08-17 03:45:40 +00:00 (Migrated from gitlab.com)

mentioned in issue #541

mentioned in issue #541
PlasticDigits commented 2026-08-26 04:08:08 +00:00 (Migrated from gitlab.com)

mentioned in issue #659

mentioned in issue #659
PlasticDigits commented 2026-08-26 04:16:12 +00:00 (Migrated from gitlab.com)

mentioned in issue #665

mentioned in issue #665
PlasticDigits commented 2026-08-26 04:22:42 +00:00 (Migrated from gitlab.com)

mentioned in issue #671

mentioned in issue #671
PlasticDigits commented 2026-08-26 04:22:45 +00:00 (Migrated from gitlab.com)

marked as related to #671

marked as related to #671
PlasticDigits commented 2026-08-26 04:23:25 +00:00 (Migrated from gitlab.com)

mentioned in issue #672

mentioned in issue #672
PlasticDigits commented 2026-08-28 05:28:48 +00:00 (Migrated from gitlab.com)

mentioned in issue #693

mentioned in issue #693
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/cl8y-dex-terraclassic#144
No description provided.