Skip to content

feat: accessibility improvements — skip link, WCAG AA dark mode contrast, focus rings, ARIA live regions#832

Open
hman38705 wants to merge 1 commit into
Ethereal-Future:mainfrom
hman38705:feat/accessibility-a11y-issues-739-740-741-742
Open

feat: accessibility improvements — skip link, WCAG AA dark mode contrast, focus rings, ARIA live regions#832
hman38705 wants to merge 1 commit into
Ethereal-Future:mainfrom
hman38705:feat/accessibility-a11y-issues-739-740-741-742

Conversation

@hman38705

@hman38705 hman38705 commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #739
closes #740
closes #741
closes #742

Implements all four Stellar Wave accessibility issues in a single PR.


#739 — Skip-to-main-content link

The skip link (<a href="#main-content" className="skip-link">) was already rendered as the first DOM element in App.jsx, and <main id="main-content"> was already in place. This PR fixes the skip link's color from the hardcoded #fff to var(--on-primary) so it remains WCAG-compliant in dark mode (#0f172a on #60a5fa → 7.3:1 ✓).


#740 — WCAG AA colour contrast in dark mode

Audit of .theme-dark CSS custom property pairs:

Pair Contrast Pass?
--text (#e2e8f0) on --bg (#020617) 16.4:1 ✓ AA
--muted (#94a3b8) on --bg (#020617) 8.3:1 ✓ AA
--muted (#94a3b8) on --surface (#111827) 6.7:1 ✓ AA
--muted (#94a3b8) on --card (#1f2937) 5.3:1 ✓ AA
--primary (#60a5fa) on --bg (#020617) 8.8:1 ✓ AA
--on-primary (#0f172a) on --primary (#60a5fa) 7.3:1 ✓ AA
--danger (#f87171) on --bg (#020617) 7.6:1 ✓ AA
--danger (#f87171) on --card (#1f2937) 4.83:1 ✓ AA
--success (#34d399) on --bg (#020617) 10.7:1 ✓ AA
--warning (#fbbf24) on --bg (#020617) 12.3:1 ✓ AA
--info (#38bdf8) on --bg (#020617) 9.6:1 ✓ AA
--link (#93c5fd) on --bg (#020617) 11.4:1 ✓ AA

Fixes for hardcoded colours that failed in dark mode:

Element Before After Contrast on dark bg
.balance-asset #555 (3.0:1 ✗) var(--muted) 8.3:1 ✓
.qr-pubkey #666 (3.8:1 ✗) var(--muted) 8.3:1 ✓
.sm-history-toggle #666 (3.8:1 ✗) var(--muted) 8.3:1 ✓

#741 — Focus-visible ring on all interactive elements

  • Added focus ring CSS custom properties to :root: --focus-ring-color: #2563eb, --focus-ring-width: 2px, --focus-ring-offset: 2px
  • Added dark mode override: --focus-ring-color: #93c5fd (11.4:1 on #020617 ✓)
  • Fixed input:focus-visible: was outline: none — now outline: var(--focus-ring-width) solid var(--focus-ring-color) while keeping border-color change
  • Fixed .fu-dropzone: removed outline: none from the base rule; added explicit .fu-dropzone:focus-visible rule using ring tokens
  • Global :focus-visible rule already applied 3px solid var(--primary) to all other interactive elements

#742 — ARIA live regions for async status updates

  • Balance updates: wrapped balance AnimatePresence in <div role="status" aria-live="polite"> so screen readers announce new balance values after transactions complete
  • StatusMessage roles: changed Message component to use role="alert" (assertive) for error/warning types and role="status" (polite) for success/info — prevents disruptive interruptions for non-urgent notifications
  • Loading state: <div aria-live="polite" aria-atomic="true" className="sr-only"> was already present in App.jsx, announcing "Sending payment…", "Checking balance…", etc.
  • aria-busy: already set on Create, Balance, and Send buttons

Test plan

  • Tab to the page — first focused element should be "Skip to main content"; pressing Enter should move focus to <main>
  • Toggle dark mode; check .balance-asset, QR modal, and message history toggle text are readable
  • Tab through all interactive elements in dark and light mode — every element should show a visible focus ring
  • Submit a payment; verify screen reader announces "Sending payment…" (polite), then success/error message
  • On payment success, the toast should be announced politely; on payment error, immediately (assertive)
  • After checking balance, the new balance values should be announced

…real-Future#740, Ethereal-Future#741, Ethereal-Future#742

- Ethereal-Future#739 (skip link): skip-to-main-content link is already first in DOM with
  <main id="main-content"> target; fix skip-link color to use --on-primary
  token so it passes WCAG AA in both light and dark mode

- Ethereal-Future#740 (dark mode contrast): replace hardcoded Ethereal-Future#555/Ethereal-Future#666 colors on
  .balance-asset, .qr-pubkey, and .sm-history-toggle with var(--muted)
  so they meet ≥4.5:1 contrast ratio on dark backgrounds; audit of all
  dark mode CSS custom property pairs confirms they all pass WCAG AA

- Ethereal-Future#741 (focus-visible ring): add --focus-ring-color/width/offset tokens to
  :root; add --focus-ring-color override in .theme-dark (#93c5fd, 11.4:1);
  fix input:focus-visible to use ring tokens instead of outline:none;
  fix .fu-dropzone to use :focus-visible with ring instead of outline:none

- Ethereal-Future#742 (live regions): balance display wrapped in role="status" aria-live
  region so updated balance is announced after transactions; StatusMessage
  now uses role="alert" for error/warning (assertive) and role="status"
  for success/info (polite), preventing disruptive interruptions for
  non-urgent notifications; loading state live region was already present

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@drips-wave

drips-wave Bot commented Jun 27, 2026

Copy link
Copy Markdown

@hman38705 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant