You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Create expense - Keyboard navigation is broken when user add a currency #96614
Version Number: 9.4.41-0 Reproducible in staging?: Yes Reproducible in production?: No If this was caught during regression testing, add the test name, ID and link from BrowserStack: Email or phone of affected tester (no customers): Logs:https://stackoverflow.com/c/expensify/questions/4856 Expensify/Expensify Issue URL: Issue reported by:@iwiznia Slack conversation (hyperlinked to channel name): #expensify_quality
👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:
Identify the pull request that introduced this issue and revert it.
⏱️ Ran out of time before I could finish. This usually means the task was too complex or I got stuck exploring. Please try again with a more specific request or post in #melvinbot-ai.
When the currency picker closes, nothing returns focus to the currency button. Focus drops to <body>, so the next Tab starts again at the top of the page. Three things combine to cause this:
The opener is lost before the picker records it. The picker wraps its content in a ScreenWrapper, and that wrapper has its own screen focus trap (ScreenWrapper/index.tsx:290). React mounts children first, so this inner trap activates before the modal's trap. On activation it blurs any focused element that isn't an input (FocusTrapForScreen/index.web.tsx:50-56). The currency button is a div with a button role, so it gets blurred. When the modal trap then records its opener (FocusTrapForModal/index.web.tsx:38), focus is already on <body>, and no launcherRef is passed to fall back on.
The replacement only restores text inputs. With that flag on, focus restore is left to ComposerFocusManager. It only remembers text inputs (ComposerFocusManager.ts:41-42), so it has nothing to restore for a button.
That explains why adding only shouldReturnFocus (the earlier proposal) didn't work in batch 5. It fixes the second cause, but the trap still has no opener to return focus to.
What changes do you think we should make in order to solve the problem?
Give the picker the currency button as its anchor, the same way PopoverMenu passes anchorRef as launcherRef:
In NumberWithSymbolForm, add an optional currencyButtonRef prop and attach it to the currency Button.
In IOURequestStepCurrencyModal, add an optional launcherRef prop. Pass it to Modal along with shouldReturnFocus={!!launcherRef}.
In AmountField, create a ref and pass it to both components.
The modal trap then falls back to the anchor when focus is on <body>, and returns focus to it on close. That path is already covered by the launcherRef fallback tests in tests/unit/FocusTrapForModalTest.tsx. The change is opt-in, so the other two callers (IOURequestStepAmount and SearchEditMultipleAmountPage) don't change. Both already refocus the amount input on purpose after the picker closes. Mouse users also see no change, because the button's onMouseDown keeps the amount input focused.
What alternative solutions did you explore? (Optional)
Alternatives, ruled-out causes, and verification status
Alternative: Pass focusTrapSettings={{active: false}} to the picker's ScreenWrapper, so its trap never blurs the button. The modal trap already keeps Tab inside the picker. I didn't choose it because it changes all three call sites, and anything else that blurs the button would break the fix again. launcherRef exists for exactly this case.
Ruled out:
The button or form remounting when the currency changes. No key depends on the currency, and the update only merges the transaction draft.
AmountField's autofocus effect. Its dependencies don't include the picker's visibility (AmountField.tsx:149-162).
Enter submitting the form. The footer's Enter shortcut is skipped while an element with a role has focus.
Verification:
Type check passes.
ESLint reports nothing new on the changed lines.
I couldn't run it in the browser. The web test session came up blank (0 DOM nodes, no network traffic), the same infra failure as the last run.
Jest isn't available in this environment, so I didn't run FocusTrapForModalTest either.
Please check the branch by hand with the issue's steps. After the picker closes, document.activeElement should be the currency button, and the next Tab should move forward from it.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/compare/main...claude-currencyPickerReturnFocus) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
Activity
melvin-bot commentedon Jul 21, 2026
Triggered auto assignment to @lakchote (
DeployBlockerCash), see https://stackoverflowteams.com/c/expensify/questions/9980/ for more details.melvin-bot commentedon Jul 21, 2026
💬 A slack conversation has been started in #expensify-open-source
github-actions commentedon Jul 21, 2026
👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:
izarutskaya commentedon Jul 21, 2026
Removed
DeployBlockerCashlabel according comment https://applause-ts.slack.com/archives/C05LX9D6E07/p1784629703276229?thread_ts=1784622088.827089&cid=C05LX9D6E07lakchote commentedon Jul 21, 2026
Coming from #96228 with the new manual expense flow
@thelullabyy please handle it.
93 remaining items
ikevin127 commentedon Oct 6, 2026
thelullabyy commentedon Oct 7, 2026
Comeback this issue today
thelullabyy commentedon Oct 7, 2026
@MelvinBot Could you please investigate and drop proposal + testing branch here?
MelvinBot commentedon Oct 7, 2026
⏱️ Ran out of time before I could finish. This usually means the task was too complex or I got stuck exploring. Please try again with a more specific request or post in #melvinbot-ai.
Run details
joekaufmanexpensify commentedon Oct 12, 2026
How's this coming?
ikevin127 commentedon Oct 12, 2026
MelvinBot commentedon Oct 12, 2026
Proposal
What is the root cause of that problem?
When the currency picker closes, nothing returns focus to the currency button. Focus drops to
<body>, so the next Tab starts again at the top of the page. Three things combine to cause this:ScreenWrapper, and that wrapper has its own screen focus trap (ScreenWrapper/index.tsx:290). React mounts children first, so this inner trap activates before the modal's trap. On activation it blurs any focused element that isn't an input (FocusTrapForScreen/index.web.tsx:50-56). The currency button is adivwith a button role, so it gets blurred. When the modal trap then records its opener (FocusTrapForModal/index.web.tsx:38), focus is already on<body>, and nolauncherRefis passed to fall back on.shouldEnableNewFocusManagement(IOURequestStepCurrencyModal.tsx:46). That turns off the trap's focus return (ReanimatedModal/index.tsx:262).ComposerFocusManager. It only remembers text inputs (ComposerFocusManager.ts:41-42), so it has nothing to restore for a button.That explains why adding only
shouldReturnFocus(the earlier proposal) didn't work in batch 5. It fixes the second cause, but the trap still has no opener to return focus to.What changes do you think we should make in order to solve the problem?
Give the picker the currency button as its anchor, the same way
PopoverMenupassesanchorRefaslauncherRef:NumberWithSymbolForm, add an optionalcurrencyButtonRefprop and attach it to the currencyButton.IOURequestStepCurrencyModal, add an optionallauncherRefprop. Pass it toModalalong withshouldReturnFocus={!!launcherRef}.AmountField, create a ref and pass it to both components.The modal trap then falls back to the anchor when focus is on
<body>, and returns focus to it on close. That path is already covered by thelauncherRef fallbacktests intests/unit/FocusTrapForModalTest.tsx. The change is opt-in, so the other two callers (IOURequestStepAmountandSearchEditMultipleAmountPage) don't change. Both already refocus the amount input on purpose after the picker closes. Mouse users also see no change, because the button'sonMouseDownkeeps the amount input focused.Testing branch:
claude-currencyPickerReturnFocus(1 commit, 3 files, +22/−2)What alternative solutions did you explore? (Optional)
Alternatives, ruled-out causes, and verification status
Alternative: Pass
focusTrapSettings={{active: false}}to the picker'sScreenWrapper, so its trap never blurs the button. The modal trap already keeps Tab inside the picker. I didn't choose it because it changes all three call sites, and anything else that blurs the button would break the fix again.launcherRefexists for exactly this case.Ruled out:
keydepends on the currency, and the update only merges the transaction draft.AmountField's autofocus effect. Its dependencies don't include the picker's visibility (AmountField.tsx:149-162).Verification:
FocusTrapForModalTesteither.Please check the branch by hand with the issue's steps. After the picker closes,
document.activeElementshould be the currency button, and the next Tab should move forward from it.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/compare/main...claude-currencyPickerReturnFocus)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run · no recording available
ikevin127 commentedon Oct 12, 2026
melvin-bot commentedon Oct 15, 2026
@ikevin127 Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
ikevin127 commentedon Oct 16, 2026
@thelullabyy I asked again, check out Melvin's proposal so we can move towards fixing this issue.