From 1159ad74a5d1be1117e0ae4f71780a4151262b2a Mon Sep 17 00:00:00 2001 From: Ioannis <2973490+ilazaridis@users.noreply.github.com> Date: Thu, 15 Oct 2026 09:03:41 +0200 Subject: [PATCH] [fix] Modal: wrap focus backwards to the last element, not its container Shift+Tab from a modal's first element wraps focus with focusLastDescendant, which tried each node before its children. Chrome and Firefox can focus a scroll container, so the wrap stopped on a ScrollView around the modal's last elements instead of the last one. Shift+Tab from there moved focus back to the first element, and the elements in the ScrollView were never reached backwards. focusLastDescendant now tries a node's children before the node, which is reverse document order. --- .../src/exports/Modal/ModalFocusTrap.js | 2 +- .../src/exports/Modal/__tests__/index-test.js | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/react-native-web/src/exports/Modal/ModalFocusTrap.js b/packages/react-native-web/src/exports/Modal/ModalFocusTrap.js index f596bf60d2..c9b3dd91fc 100644 --- a/packages/react-native-web/src/exports/Modal/ModalFocusTrap.js +++ b/packages/react-native-web/src/exports/Modal/ModalFocusTrap.js @@ -58,7 +58,7 @@ function focusFirstDescendant(element: any) { function focusLastDescendant(element: any) { for (let i = element.childNodes.length - 1; i >= 0; i--) { const child = element.childNodes[i]; - if (attemptFocus(child) || focusLastDescendant(child)) { + if (focusLastDescendant(child) || attemptFocus(child)) { return true; } } diff --git a/packages/react-native-web/src/exports/Modal/__tests__/index-test.js b/packages/react-native-web/src/exports/Modal/__tests__/index-test.js index f053074880..b250918b0a 100644 --- a/packages/react-native-web/src/exports/Modal/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/Modal/__tests__/index-test.js @@ -532,6 +532,33 @@ describe('components/Modal', () => { expect(document.activeElement).toBe(insideEndElement); }); + test('focus wraps backwards to the last element inside a focusable container', () => { + render( + <> + + + Inside A + +
+ + Inside B + +
+
+ + ); + + const insideStartElement = document.querySelector( + '[data-testid="inside-a"]' + ); + const insideEndElement = document.querySelector('[data-testid="inside-b"]'); + const focusBracket = + insideStartElement.parentNode.parentNode.parentNode.previousSibling; + insideStartElement.focus(); + focusBracket.focus(); + expect(document.activeElement).toBe(insideEndElement); + }); + test('focus is trapped without contents', () => { render( <>