fix: Screen Reader: Many Pages: There is no dialog role and title announced#85221
Conversation
|
@mkhutornyi Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 43d1d07094
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
…Reader-Many-Pages-There-is-no-dialog-role-and-title-announced
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 95126f75b4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Ready for review. You can ignore the Jest, it's failing on main. |
|
Please summarize new fixes with root cause |
|
@MelvinBot please help with #85221 (comment) |
Summary of Changes and Root CausesOriginal Issue (#76944)Problem: When opening Right-Hand Panel (RHP) pages, screen readers (JAWS, VoiceOver, NVDA) do not announce the dialog role or title, violating WCAG 4.1.2 (Name, Role, Value). Root Cause: The RHP container ( Fix Breakdown1.
2.
3.
4.
5.
Why this approach avoids the regression issuesThe many regression tests (#84957, #84958, #84960, #84966, #84972, etc. — blank pages, lost focus, sluggish animation) all relate to the same concern: the accessibility additions must not interfere with existing navigation and rendering behavior. The key design decisions that prevent regressions:
|
…Reader-Many-Pages-There-is-no-dialog-role-and-title-announced
|
Jest fail is gone. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3dfef9c6f2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Performance regression while testing #85007: this branch: popover is not opened immediately, checkbox is not unchecked immediately Screen.Recording.2026-03-15.at.2.45.05.am.movmain: Screen.Recording.2026-03-15.at.2.47.02.am.mov |
staging or dev? |
|
|
Could you share a video? |
|
Video doesn't help because accessibility is broken while screen recording is on Screen.Recording.2026-04-06.at.9.41.27.pm.mov |
This comment was marked as outdated.
This comment was marked as outdated.
|
Ok I see. Its very confusing |
|
Could you give it a try? After the back button announcement there should be an announcement that announces its in dialog |
|
Feel free to test @amyevans |
…Reader-Many-Pages-There-is-no-dialog-role-and-title-announced
…Reader-Many-Pages-There-is-no-dialog-role-and-title-announced # Conflicts: # src/components/HeaderWithBackButton/index.tsx
|
🚧 @amyevans has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
@TaduJR please check this. Back button is highlighted after refresh app Screen.Recording.2026-04-07.at.6.05.44.pm.mov |
|
Sure. We will handle it in the next child issue, or should I create a separate PR. LMK! |
Maybe separate PR. Please fix as priority. QA might report this as blocker. |
On it now. |
PR is ready. |
|
🚀 Deployed to staging by https://github.com/amyevans in version: 9.3.54-0 🚀
Bundle Size Analysis (Sentry): |
|
No help site changes are required for this PR. This PR adds screen reader accessibility (ARIA |
|
Deploy Blocker #87304 was identified to be related to this PR. |
|
🚀 Deployed to production by https://github.com/roryabraham in version: 9.3.54-7 🚀
|
Explanation of Change
When opening RHP pages, screen readers (JAWS, VoiceOver) don't announce the dialog role or title, violating WCAG 4.1.2 (Name, Role, Value).
This PR adds dialog semantics to the RHP container and moves focus to the first interactive element for screen reader announcement.
What's changed
DialogLabelContext- A ref-based label stack that communicates the screen title fromHeaderto the RHP container. Uses direct DOM mutation (setAttribute) to updatearia-label, avoiding React state updates during animation. Split into data and actions contexts per lint rules.RHP Animated.View - Gets
role="dialog",aria-modal="true", andaria-label(via DOM mutation) on wide layouts. Small screens already get dialog semantics fromcreateModalStackNavigator.DialogLabelProviderwraps theStack.Navigatorinside the Animated.View.useDialogLabelRegistration- Hook that encapsulates the label lifecycle (push on mount, pop on unmount). Only registers whenisScreenHeaderis true, preventing nestedHeadercomponents (e.g., inside PDF modals) from overwriting the dialog label.useDialogContainerFocus- Platform-specific hook (web only, no-op on native) that focuses the first interactive element (typically the back button) inside the dialog after the screen transition completes. UsesInteractionManager+requestAnimationFrameto defer pastuseAutoFocusInput's async focus chain, andpreventScroll: trueto avoid layout recalculation during close animation. Skips focus if another element already has focus. Filters out stacked screens viaaria-hidden="true".Header- AcceptsisScreenHeaderprop (set byHeaderWithBackButton) to gate dialog label registration and focus. Only screen-level headers participate in the dialog system.HeaderWithBackButton- PassesisScreenHeadertoHeader. For avatar-header routes (e.g.,MoneyReportHeader) that skipHeader, registersreport?.reportNameas the dialog label directly.Fixed Issues
$ #76944
PROPOSAL: #76944 (comment)
Tests
Setup: Use web (wide layout). Enable a screen reader (VoiceOver on macOS, or JAWS/NVDA on Windows).
Accessibility verification:
Regression tests (screen reader can be off):
#85013 Sluggish RHP close animation:
#84960 Amount input field loses focus:
#84958 Name field auto throws error:
#84957 Blank page on Clear cache and restart:
#84966 Blank page when deleting expense:
#84972 Blank page on disabling Approvals:
#84975 Long click on Inbox chats:
#84995 Blank page after importing Per diem CSV:
#85007 Removing a member crashes the page:
#85017 Blank page on deleting contact method:
#85019 Cancel payment leads to blank page:
#85033 Delete bank account blank page:
#85045 Sign out offline shows blank page:
Offline tests
Same as tests
QA Steps
// TODO: These must be filled out, or the issue title must include "[No QA]."
Same as tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectioncanBeMissingparam foruseOnyxtoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
Mac-Chrome.1.mp4