[iOS][Fabric] Modal content vanishes from the accessibility tree on repeated present/dismiss cycles (renders on screen, invisible to XCUITest/VoiceOver; dev builds)
Personne n'a encore pris cette issue.
- Langage dominant
- C++
- Étoiles
- 127k
- Forks
- 25.3k
- Merge moyen
- 1 j 23 h
- PR mergées (30 j)
- 4
Description
Description
On the new architecture (Fabric), after one successful present/dismiss cycle of a <Modal transparent animationType="slide">, the next presentation renders on screen but registers nothing in the accessibility tree — the AX tree contains only status-bar/system elements. Touch still works: tapping the modal's close button by coordinates dismisses it and fully restores the accessibility tree; presenting again after that heal is blind again.
Reproduced in a bare community-CLI template (no Expo, no navigation library, one Modal), on both 0.86.2 and 0.87.0, using an XCUITest-based driver (Maestro) as the accessibility ground truth:
- Fresh launch → presentation 1 fully accessible → presentation 2 blind. Deterministic across app relaunches (Debug builds).
maestro hierarchyin the blind state shows only the status bar (time, battery, cellular) and the app root — no modal subtree, no base-screen elements.- Dismissing by coordinate tap restores everything; the next presentation is blind again.
- Build-mode asymmetry: Debug reproduces on the 2nd presentation every time; Release stayed clean for 40 cycles on the same app/simulator.
The failure state lives in the app process, not the test driver:
- Each open/close cycle above runs in its own
maestro testinvocation (fresh driver process each time) and the poison persists across them. - In the production app where we first hit this (larger surface, several modal hosts), a fresh XCUITest runner still sees the blind tree, and killing/respawning
testmanagerdmid-blindness changes nothing. There the trigger index varied slightly (2nd–3rd presentation, including cross-modal-host sequences: sheet ✓ → menu ✗, and menu ✓ menu ✓ → sheet ✗), so the exact index looks timing-sensitive; the minimal app settles on "2nd presentation". - The unified log shows correctly paired
kAXUserTestingNotificationpayloads (RCTFabricModalHostViewControllerViewDidAppear ↔ ViewDidDisappear) for every presentation including the blind ones — the modal VC lifecycle looks healthy while AX registration is missing. - Waiting 45s in the blind state does not self-heal; only dismissal heals.
AccessibilityInfo.setAccessibilityFocuson the modal content inonShowdoes not prevent it.
Impact: for assistive-technology users on dev builds, a blind modal's close control is not an accessibility element, so there is no announced way out (we could not script VoiceOver on the simulator to confirm end-to-end; the tree evidence above is from XCUITest snapshots, which share the AX infrastructure). For teams running XCUITest/Maestro/Appium suites against dev builds, any test past the second modal presentation fails on missing elements. Since Release builds did not reproduce in our runs, production users are plausibly unaffected — but we have not found what dev-mode dependency gates the bug, so we can't say that with confidence.
Possibly related, but describing different symptoms: #50152 (invisible undismissed modal layer), #48611, #49717.
Steps to reproduce
- Clone https://github.com/hubyrod/rn-modal-a11y-repro (bare
cli inittemplate + oneApp.tsx: a button presenting a transparent slide-animation Modal with a close button; Maestro flows included). npm install && cd ios && pod install && cd ..npx react-native run-ios --mode Debug(iOS simulator).maestro test cycle.yaml— passes (open → close via accessibility taps).maestro test cycle.yamlagain — fails: the modal is visibly presented but "CLOSE" is not in the accessibility tree.maestro hierarchyshows only status-bar elements.maestro test close-by-point.yaml(coordinate tap) — the modal dismisses and the tree is restored.maestro test open-only.yaml— blind again.
React Native Version
0.86.2 (also reproduced identically on a fresh 0.87.0 template)
Affected Platforms
Runtime - iOS, Build - MacOS
Output of npx react-native info
System:
OS: macOS 26.6.2
CPU: (14) arm64 Apple M4 Pro
Memory: 3.53 GB / 48.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 22.14.0
path: /Users/hugo/.nvm/versions/node/v22.14.0/bin/node
Yarn:
version: 1.22.22
path: /opt/homebrew/bin/yarn
npm:
version: 11.3.0
path: /Users/hugo/.nvm/versions/node/v22.14.0/bin/npm
Watchman: Not Found
Managers:
CocoaPods:
version: 1.17.0
path: /opt/homebrew/bin/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 25.5
- iOS 26.5
- macOS 26.5
- tvOS 26.5
- visionOS 26.5
- watchOS 26.5
Android SDK: Not Found
IDEs:
Android Studio: 2025.2 AI-252.28238.7.2523.14688667
Xcode:
version: 26.6/17F113
path: /usr/bin/xcodebuild
Languages:
Java:
version: 17.0.20
path: /usr/bin/javac
Ruby:
version: 4.0.6
path: /opt/homebrew/bin/ruby
npmPackages:
"@react-native-community/cli":
installed: 20.1.0
wanted: 20.1.0
react:
installed: 19.2.3
wanted: 19.2.3
react-native:
installed: 0.86.2
wanted: 0.86.2
react-native-macos: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: true
newArchEnabled: true
iOS:
hermesEnabled: true
newArchEnabled: true
Simulator: iPhone 17, iOS 26.2 runtime. AX probe: Maestro 2.4.0 (XCUITest driver).
Stacktrace or Logs
No crash. Accessibility hierarchy in the blind state (modal visibly presented,
screenshot available) reduces to status-bar/system elements only:
"accessibilityText" : "02:13" — status bar time
"accessibilityText" : "100 % battery power" — status bar
"accessibilityText" : "Cellular" — status bar
"accessibilityText" : "ModalA11yRepro" — app root node (no children with content)
After a coordinate-tap dismissal, the same probe returns the full tree
(OPEN button, counter text). Unified log shows paired
kAXUserTestingNotification payloads from RCTFabricModalHostViewController
(ViewDidAppear/ViewDidDisappear) for blind presentations as well.
Reproducer
https://github.com/hubyrod/rn-modal-a11y-repro
Screenshots and Videos
Screenshot of the blind state (modal fully rendered, presentations: 2) alongside the status-bar-only hierarchy dump is available on request — the reproducer regenerates both in under a minute.
Guide de contribution
Ouvrir le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Piste de recherche
Commencez par le rn-modal-a11y-repro lié : exécutez cycle.yaml dans une build Debug iOS et comparez la première et la deuxième présentation avec la hiérarchie Maestro. Suivez ensuite le cycle de vie de la modale Fabric autour de RCTFabricModalHostViewController et les notifications d’accessibilité signalées ; le travail est terminé lorsque des cycles répétés de présentation/fermeture maintiennent la modale et les éléments de l’écran de base dans l’arbre d’accessibilité, y compris le flux existant piloté par l’accessibilité.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- react-native
- Domaine
- accessibility, mobile
- Type d'issue
- Bug
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Activité
- Active
- Clarté
- Plutôt claire
- Accessibilité débutants
- 52/100