feat(frontend): keyboard shortcuts + live-region announcements across 4 components - #1279
Merged
leojay-net merged 4 commits intoJul 26, 2026
Conversation
(closes leojay-net#1184) "Enter" and "R" retry from the error fallback the same way the button's onClick does — only active while the fallback is actually showing, and ignored while focus is in a form field so it can't hijack typing. Shares a single retry() method with the button's onClick rather than duplicating the retry-vs-reload branch. Added a visible <kbd> hint next to the button, styled for both light/dark via the existing isDarkMode prop. No animation involved, so prefers-reduced-motion doesn't apply here.
(closes leojay-net#1185) "g" navigates to /chat (same as the Get Started CTA), "d" toggles the theme via the existing ThemeContext. Both ignored while focus is in the email field (or any other form control) so typing there isn't hijacked. No animation involved, so prefers-reduced-motion doesn't apply here.
(closes leojay-net#1183) ArrowDown/ArrowUp move an active-result index (wrapping), Enter selects the active result, Escape still closes the panel as before. Active result gets a visible highlight (theme-aware) plus role="listbox" / role="option" + aria-selected on the results list so the active item is exposed to assistive tech, not just visually. No animation involved, so prefers-reduced-motion doesn't apply here.
(closes leojay-net#1179) None of the bridgeState sections (optimistic/initiating/polling/success/ error) were in an aria-live container, so a screen reader user starting a CCIP transfer heard nothing as it moved through those states — only a separate "network changed" banner had aria-live. Added a single sr-only role="status" aria-live="polite" region that announces a message derived from bridgeState/latestStatus/errorMessage on every transition. Adjusted 3 pre-existing test assertions (getByText -> getAllByText[0]) that broke because the new live region's text now also contains the same substrings those regex queries were matching against the visible UI — both matches are correct (the info is intentionally shown twice: visibly and to assistive tech), so getByText's "exactly one match" requirement was the thing that needed to change, not the component.
|
@abayomicornelius 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! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
#1184 — ErrorBoundary.
Enter/Rretry from the error fallback (only while it's showing, ignored while focus is in a form field). Shares the retry logic with the existing button'sonClickrather than duplicating it. Added a visible<kbd>hint.#1185 — LandingPage.
gnavigates to/chat(same as the Get Started CTA),dtoggles the theme. Both ignored while focus is in the email field or any other form control.#1183 — ChatSearchPanel.
ArrowDown/ArrowUpmove an active-result index (wraps around),Enterselects it,Escapestill closes the panel. Addedrole="listbox"/role="option"+aria-selectedso the active item is exposed to assistive tech, not just visually highlighted.#1179 — CCIPBridgeModal. None of the bridge-state sections (
optimistic/initiating/polling/success/error) were in anaria-livecontainer — a screen reader user starting a transfer heard nothing as it progressed. Added a singlesr-onlyrole="status" aria-live="polite"region that announces a derived message on every state transition.All four: theme-aware via the existing
ThemeContext/isDarkModepatterns already in each file, no animation involved in any of the additions (soprefers-reduced-motiondoesn't apply), and none change existing behavior for mouse/click users.Closes #1179
Closes #1183
Closes #1184
Closes #1185
Test plan
npx tsc --noEmit: cleannpx eslinton all changed files: clean (fixed onejsx-a11y/role-supports-aria-propswarning by movingaria-selectedfrom the<button>to the<li role="option">it actually belongs on)npx vitest runon the 4 changed test files: 51/51 pass, including 3 pre-existingCCIPBridgeModal.test.tsxassertions adjusted fromgetByTexttogetAllByText(...)[0]— they broke because the new live region's announcement text now also contains the same substrings those queries matched against the visible UI (both matches are correct; a single-match assumption was the thing that needed to change)npx vitest run(730 tests): ran twice: each run had 2 failures, but a different 2 each time, including one inSplitViewComparison.test.tsx— a file untouched by this PR — confirming pre-existing flakiness under full-suite parallel load, not something introduced here. All 4 of this PR's own test files pass reliably in isolation.