fix: The Language dropdown has no role, name and state announced#80713
fix: The Language dropdown has no role, name and state announced#80713JS00001 merged 22 commits intoExpensify:mainfrom
Conversation
|
Hey! I see that you made changes to our Form component. Make sure to update the docs in FORMS.md accordingly. Cheers! |
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
joekaufmanexpensify
left a comment
There was a problem hiding this comment.
Fine from a product perspective
|
@nkdengineer We're we going to implement:
? |
|
@Ollyws Here is the text reader from Voice over, when we focus on the picker, the selected Langue is already a text of the reader then we only need to add the Screen.Recording.2026-02-03.at.13.15.46.mov |
|
@nkdengineer Isn't it supposed to say the status of the dropdown too? |
|
@Ollyws Isn't it the status of the dropdown? |
|
@nkdengineer But that behaviour only applies to web right? On mobile platforms is just anounces the title of the dropdown. |
@Ollyws Can you share a video? |
|
@nkdengineer On iOS in the accesibility inspector the value is displaying as empty and it just announces the title:
|
|
@Ollyws You're right, I updated. |
|
Thanks for the update and it's going in the right direction, but on the original issue the expected outcome is:
|
|
@Ollyws On dev testing, I can't focus on the picker on mWeb |
|
@MelvinBot Why would the picker be announcing 'collapsed' on android talkback mWeb but not on native? |
|
This comes down to a fundamental difference in how accessibility state is managed on mWeb vs native Android. On mWeb (Android TalkBack in Chrome): The browser natively manages expanded/collapsed state for elements with On native Android: React Native has no built-in "combobox" concept that automatically tracks expanded/collapsed state. Previously, the component used The issue is that
So on native, TalkBack may not be re-announcing the label when the picker opens/closes, whereas on mWeb the browser handles it natively through the combobox role's built-in state management. |
|
@MelvinBot What changes would you suggest to fix this discrepancy? |
|
I'd suggest a two-part fix: 1. Restore
|
| Platform | Expanded/collapsed via | State text in label? |
|---|---|---|
| Native (Android/iOS) | accessibilityState.expanded on touchableWrapper |
No |
| mWeb | Text in label (renderWeb() ignores touchableWrapperProps) |
Yes |
| Desktop web | Native <select> combobox behavior |
No |
This way each platform uses the correct mechanism: native platforms get the semantic accessibilityState that TalkBack/VoiceOver understand natively, while mWeb gets the text-based fallback since it's the only available mechanism there.
|
@MelvinBot |
|
Right — this is because of two overlapping announcements on mWeb:
So TalkBack reads both — the explicit text from the label AND the browser's native state announcement. The fix would be to add a separate flag (e.g. |
|
@MelvinBot Ok so fix your proposal... |
|
Updated proposal — the key insight from your feedback is that no platform needs the expanded/collapsed text in the label, so we can simplify significantly:
Changes1. Restore touchableWrapperProps={{
accessible: true,
accessibilityRole: CONST.ROLE.COMBOBOX,
accessibilityLabel: actualAccessibilityLabel,
accessibilityState: {disabled: isDisabled, expanded: isHighlighted},
}}2. Remove the expanded/collapsed text from const enhancedAccessibilityLabel = useMemo(() => {
if (!defaultAccessibilityLabel) {
return selectedLabel || '';
}
if (selectedLabel) {
return `${defaultAccessibilityLabel}${shouldAnnounceSelectedLabel ? `, ${selectedLabel}` : ''}`;
}
return defaultAccessibilityLabel;
}, [defaultAccessibilityLabel, selectedLabel, shouldAnnounceSelectedLabel]);3. Remove Result
No double announcements on any platform, and each platform uses its proper native mechanism. |
|
@MelvinBot This doesn't announce 'collapsed/expanded/ for ios safari |
|
On iOS Safari, VoiceOver handles The root cause: On web, the picker renders as a native HTML
The fix would be to add an explicit pickerProps={{
// ... existing props
accessibilityExpanded: isHighlighted,
}}This would give VoiceOver the semantic signal it needs to announce the state, rather than relying on it being embedded in the label text. |
|
@MelvinBot accessibilityExpanded doesn't work on Safari with RNPickerSelect |
|
That's correct —
So the PR's approach of embedding "Collapsed"/"Expanded" text in the |
|
@MelvinBot is there no way to stop all web announcing collapsed/expanded? |
|
No — the browser's native collapsed/expanded announcement for However, the concern this raises is about double announcements on mWeb. Currently, when return `${defaultAccessibilityLabel}${shouldAnnounceSelectedLabel ? `, ${selectedLabel}` : ''}, ${translate(isHighlighted ? 'common.expanded' : 'common.collapsed')}`;Since the browser also natively announces collapsed/expanded for the combobox role, mWeb users would hear it twice — once from the custom label text and once from the browser's native behavior. The fix would be to add a separate flag (e.g.,
|
|
@MelvinBot |
|
You're right — my previous answer was inaccurate. Let me correct it. Looking at the code more carefully: On mWeb, The custom label does include "Collapsed/Expanded" text on mWeb (since
The root issue is that the This may need a web-specific approach like explicitly setting |
Reviewer Checklist
Screenshots/VideosAndroid: HybridApp01_Android_Native.mp4Android: mWeb Chrome02_Android_Chrome.mp4iOS: HybridApp03_iOS_Native.mp4iOS: mWeb Safari04_iOS_Safari.mp4MacOS: Chrome / SafariN/A |
There was a problem hiding this comment.
LGTM.
With regard to 'collapsed' being announced twice on Android mWeb this is an upstream limitation and the only alternative would be to have 'collapsed' not be anounced atall on iOS Safari so we have gone with the safest option, albeit imperfect.
|
thank you for exploring that, I agree with where we've landed |
|
🚧 @JS00001 has triggered a test Expensify/App build. You can view the workflow run here. |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/JS00001 in version: 9.3.42-0 🚀
Bundle Size Analysis (Sentry): |
|
🚀 Deployed to production by https://github.com/grgia in version: 9.3.42-3 🚀
|

Explanation of Change
fix: The Language dropdown has no role, name and state announced
Fixed Issues
$ #77556
PROPOSAL: #77556 (comment)
Tests
Offline tests
QA Steps
// TODO: These must be filled out, or the issue title must include "[No QA]."
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
Screen.Recording.2026-01-28.at.23.50.02.mov