From 96f3b4c03fcdd19122877a64d50c4dc23e0b5838 Mon Sep 17 00:00:00 2001 From: GitHub Date: Tue, 28 Jan 2025 15:23:08 +1100 Subject: [PATCH 1/7] Shadow DOM RFC --- rfcs/2025-shadow-dom-support.md | 120 ++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 rfcs/2025-shadow-dom-support.md diff --git a/rfcs/2025-shadow-dom-support.md b/rfcs/2025-shadow-dom-support.md new file mode 100644 index 00000000000..8c3dfb0bc20 --- /dev/null +++ b/rfcs/2025-shadow-dom-support.md @@ -0,0 +1,120 @@ + + +- Start Date: 2025/1/28 +- RFC PR: exploration PRs: https://github.com/adobe/react-spectrum/pull/6046 +- Authors: Rob Snow + +# Improving React Aria Shadow DOM Support + +## Summary + +This RFC outlines a plan to incorporate Shadow DOM support in React Aria, React Aria Components, and React Spectrum S2. +Shadow DOM support can mean many things; open root vs closed root, single containing shadow root, or multitude of individual components. +As a result, this RFC aims to specifically support single containing shadow root, with some improvements to closed root individual components. It is also intended to ensure we continue support into the future. + +## Motivation + + +As Web Components have become more prominent and more libraries and applications use them, we have encountered friction for people trying to adopt or use our libraries alongside others. + +Some examples of these are: +- [Dialog's focus management and work with 3rd party dialogs](https://github.com/adobe/react-spectrum/issues/5314) +- [Video Controls are not respected when using FocusScope](https://github.com/adobe/react-spectrum/issues/6729) +- [FocusScope not working when used inside shadowRoot](https://github.com/adobe/react-spectrum/issues/1472) +- [ariaHideOutside incorrect behavior inside shadow DOM.](https://github.com/adobe/react-spectrum/issues/6133) +- [usePress is not work in shadowRoot](https://github.com/adobe/react-spectrum/issues/2040) +- [useOverlay Click Outside in Shadow-DOM context](https://github.com/adobe/react-spectrum/issues/3970) + +We have also had a contribution to solve some of the issues. While this is useful, we would like it to feel less hacky and more importantly, we'd like to incorporate the support into our daily lives in as easy as way as possible. + + +## Detailed Design + +As mentioned earlier, there are proposed parts to this initiative: + +1. Custom React Testing Library renderer + +Much like our custom renderer to test React.StrictMode, we should create a custom React Testing Library renderer for our unit tests which can wrap each test's rendered dom in a shadow root. + +This will give us a baseline to develop against and it will also hold us accountable in any future changes without needing to write many specific tests. In the worst case, should we pull the plug on this, it will also make it easy to remove the tests. + +I expect many tests will fail in the beginning. We make use of a lot of DOM API's and have not generally thought of the ShadowDOM while developing. + +2. Rework code not to need to dive into Shadow Roots. + +This is most prominent in Focus Scope where we traverse the DOM in order to assign focus, such as in Collections, and contain focus such as in Dialogs. + +One proposal to avoid this traversal is to create focusable sentinels in order to trap focus. + +This will have the side benefit of theoretically working with closed root + +3. Education + +Writing code that can handle the prescence of Shadow DOM can be tricky. As a result, there will be a learning curve for the team. + +Some work has already been done to write utilities to hide away some of this complexity. We will also want to write lint rules to help us automatically catch as many common situations as possible. Some examples of these are: + +```jsx +document.activeElement +<-> +getActiveElement(); + +e.target +<-> +getTargetElement(e); + +let walker = document.createTreeWalker(...) +<-> +let walker = createShadowTreeWalker(...) + +e.currentTarget.contains(e.target) +<-> +nodeContains(e.currentTarget, getEventTarget(e.nativeEvent)) +``` + +4. Communication + +We have our current Shadow DOM support gated behind a feature flag to both signal that it is experimental as well as to protect users who are not using any Shadow DOM from possible performance hits. + +We will want to expand this to also shield those users from code size increases. This could be accomplished with a global registry for the utility functions we provide that would replace the feature flag system we have for it right now. + +We also need to communicate, through documentation, what the specifics are of our Shadow DOM support since we likely will not be able to support everything, and certainly not much for a while. + +## Drawbacks + +One concern is ongoing support. Our current use cases do not call for Shadow DOM support, so knowledge on the team is currently thin. It will take time for people to ramp up on skills. Not only that, but it isn't part of our weekly testing and I have no plans for it to be at this time, which means we must rely on unit tests as much as possible. + +In addition, contributions may occur for a little while until those teams have their needs met, but leave the code base in an unfinished state for complete support aligning with scope of goals as outlined here. + +Another concern is that the current approach is, for lack of a better word, hacky. This is because we are accessing and manipulating the Shadow DOM in ways that it wasn't really intended. If we were to rewrite our library today, there are other ways we'd solve these issues which would respect more of the concept of the Shadow DOM and its purpose. What we do here and now may complicate a future where we had different APIs to support this vision of what support would ideally look like. + + +## Backwards Compatibility Analysis + +This is a backwards compatible change, we should just be extending functionality, not breaking any of it. + +## Alternatives + +Unknown, haven't done research here yet. + +## Open Questions + +* How to actually define the limitations of our support? +* + +## Help Needed + +The biggest help we can receive is tests, either in the form of unit tests or in the form of examples of real life applications/setups that we can turn into unit tests. The more tests we have, the less likely we will break anything moving forward after the initial effort is complete. + +## Frequently Asked Questions + +## Related Discussions + +Original issue: [feat: Focus Management within ShadowDOM](https://github.com/adobe/react-spectrum/pull/6046). Many of the ideas discussed in this RFC are from conversations around this PR. From e505e3da266ff5ec86ca4fa67e75b6df847c5ead Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 30 Jan 2025 14:35:58 +1100 Subject: [PATCH 2/7] Update RFC --- rfcs/2025-shadow-dom-support.md | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/rfcs/2025-shadow-dom-support.md b/rfcs/2025-shadow-dom-support.md index 8c3dfb0bc20..9094bcd56de 100644 --- a/rfcs/2025-shadow-dom-support.md +++ b/rfcs/2025-shadow-dom-support.md @@ -15,12 +15,12 @@ governing permissions and limitations under the License. --> ## Summary -This RFC outlines a plan to incorporate Shadow DOM support in React Aria, React Aria Components, and React Spectrum S2. +This RFC outlines a plan for progressive enhancement of Shadow DOM support in React Aria, React Aria Components, and React Spectrum S2. Shadow DOM support can mean many things; open root vs closed root, single containing shadow root, or multitude of individual components. -As a result, this RFC aims to specifically support single containing shadow root, with some improvements to closed root individual components. It is also intended to ensure we continue support into the future. +We can improve across all of these. -## Motivation +## Motivation As Web Components have become more prominent and more libraries and applications use them, we have encountered friction for people trying to adopt or use our libraries alongside others. @@ -39,7 +39,7 @@ We have also had a contribution to solve some of the issues. While this is usefu As mentioned earlier, there are proposed parts to this initiative: -1. Custom React Testing Library renderer +1. Custom React Testing Library Renderer Much like our custom renderer to test React.StrictMode, we should create a custom React Testing Library renderer for our unit tests which can wrap each test's rendered dom in a shadow root. @@ -47,15 +47,15 @@ This will give us a baseline to develop against and it will also hold us account I expect many tests will fail in the beginning. We make use of a lot of DOM API's and have not generally thought of the ShadowDOM while developing. -2. Rework code not to need to dive into Shadow Roots. +2. Avoid DOM Traversal/Manipulation This is most prominent in Focus Scope where we traverse the DOM in order to assign focus, such as in Collections, and contain focus such as in Dialogs. One proposal to avoid this traversal is to create focusable sentinels in order to trap focus. -This will have the side benefit of theoretically working with closed root +This will have the side benefit of theoretically working with closed root shadow doms as well, such as the aforementioned native video players inside a Dialog. -3. Education +3. In-team Education Writing code that can handle the prescence of Shadow DOM can be tricky. As a result, there will be a learning curve for the team. @@ -87,13 +87,15 @@ We will want to expand this to also shield those users from code size increases. We also need to communicate, through documentation, what the specifics are of our Shadow DOM support since we likely will not be able to support everything, and certainly not much for a while. +For example, some of our support may require open root Shadow DOMs. Or someone using our FocusScope's focus manager may need to know about different traversal methods to avoid the same issues we are trying to avoid. + ## Drawbacks -One concern is ongoing support. Our current use cases do not call for Shadow DOM support, so knowledge on the team is currently thin. It will take time for people to ramp up on skills. Not only that, but it isn't part of our weekly testing and I have no plans for it to be at this time, which means we must rely on unit tests as much as possible. +One concern is on-going support. Our current use cases do not call for Shadow DOM support, so knowledge on the team is currently thin. It will take time for people to ramp up on skills. Not only that, but it isn't part of our weekly testing and we have no plans for it to be at this time, which means we must rely on unit tests as much as possible. -In addition, contributions may occur for a little while until those teams have their needs met, but leave the code base in an unfinished state for complete support aligning with scope of goals as outlined here. +In addition, contributions may occur for a little while until those teams have their needs met. However, it should be assumed that there will be further work to complete the goals as outlined here. -Another concern is that the current approach is, for lack of a better word, hacky. This is because we are accessing and manipulating the Shadow DOM in ways that it wasn't really intended. If we were to rewrite our library today, there are other ways we'd solve these issues which would respect more of the concept of the Shadow DOM and its purpose. What we do here and now may complicate a future where we had different APIs to support this vision of what support would ideally look like. +Another concern is that the current approach is, for lack of a better word, hacky. This is because we are accessing and manipulating the Shadow DOM in ways that it wasn't really intended. If we were to rewrite our library today, there are other ways we'd solve these issues which would respect more of the concept of the Shadow DOM and its purpose. What we do here and now may complicate a future where we have different APIs to support this vision of what support would ideally look like. ## Backwards Compatibility Analysis @@ -106,8 +108,7 @@ Unknown, haven't done research here yet. ## Open Questions -* How to actually define the limitations of our support? -* +* How to actually define the limitations of our support? See Introduction, it's missing a final sentence with this information. ## Help Needed @@ -115,6 +116,13 @@ The biggest help we can receive is tests, either in the form of unit tests or in ## Frequently Asked Questions +* How much can we count on contributor support? will the code just rot after the initial push? + * We should expect that we'll take ownership of any code that comes in, we should not count on external support. +* Is there any benefit to our selves or other people not using Shadow DOM? + * Yes, see some of the issues listed above, specifically native video players breaking FocusScopes in Dialogs. + * Users may be unaware that they are using Shadow DOM as it may be an implementation detail of a 3rd party component. + + ## Related Discussions Original issue: [feat: Focus Management within ShadowDOM](https://github.com/adobe/react-spectrum/pull/6046). Many of the ideas discussed in this RFC are from conversations around this PR. From 5125ea579f640988b7df595fdd1b6f6118c2061a Mon Sep 17 00:00:00 2001 From: GitHub Date: Mon, 10 Feb 2025 14:45:56 +1100 Subject: [PATCH 3/7] clarify motivation --- rfcs/2025-shadow-dom-support.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/2025-shadow-dom-support.md b/rfcs/2025-shadow-dom-support.md index 9094bcd56de..5b10fb61c30 100644 --- a/rfcs/2025-shadow-dom-support.md +++ b/rfcs/2025-shadow-dom-support.md @@ -22,7 +22,7 @@ We can improve across all of these. ## Motivation -As Web Components have become more prominent and more libraries and applications use them, we have encountered friction for people trying to adopt or use our libraries alongside others. +As Shadow DOM is used by more libraries and applications, users have encountered friction trying to adopt or use our libraries. This might be using another 3rd party library that wants to prevent outside styles from affecting their components. It might be the usage of web components. Or, it might be just using native controls such as the `video` tag. Some examples of these are: - [Dialog's focus management and work with 3rd party dialogs](https://github.com/adobe/react-spectrum/issues/5314) From 7a4e44f46a3dd4da17b8259d94a554521c640dce Mon Sep 17 00:00:00 2001 From: Robert Snow Date: Mon, 22 Dec 2025 13:51:35 +1100 Subject: [PATCH 4/7] Add a little detail to testing as I start exploring --- rfcs/2025-shadow-dom-support.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rfcs/2025-shadow-dom-support.md b/rfcs/2025-shadow-dom-support.md index 5b10fb61c30..c8a8ec3d24b 100644 --- a/rfcs/2025-shadow-dom-support.md +++ b/rfcs/2025-shadow-dom-support.md @@ -47,6 +47,9 @@ This will give us a baseline to develop against and it will also hold us account I expect many tests will fail in the beginning. We make use of a lot of DOM API's and have not generally thought of the ShadowDOM while developing. +A first go at it can be found here: https://github.com/adobe/react-spectrum/compare/get-tests-running-in-shadowdom?expand=1 +It unfortunately appears that we cannot just keep our existing tests, there's just too many differences. + 2. Avoid DOM Traversal/Manipulation This is most prominent in Focus Scope where we traverse the DOM in order to assign focus, such as in Collections, and contain focus such as in Dialogs. @@ -109,11 +112,14 @@ Unknown, haven't done research here yet. ## Open Questions * How to actually define the limitations of our support? See Introduction, it's missing a final sentence with this information. +* User Event and JSDOM do not have good shadow DOM support, and user event has not been accepting PRs, can we count of them when testing? https://github.com/testing-library/user-event/issues/1026 ## Help Needed The biggest help we can receive is tests, either in the form of unit tests or in the form of examples of real life applications/setups that we can turn into unit tests. The more tests we have, the less likely we will break anything moving forward after the initial effort is complete. +"shadow-dom-testing-library" can be used in place of functions from test-library. + ## Frequently Asked Questions * How much can we count on contributor support? will the code just rot after the initial push? From 1f67bd0d4610d93113e2363ccc3a08730bc59dd7 Mon Sep 17 00:00:00 2001 From: Robert Snow Date: Mon, 5 Jan 2026 16:36:23 -0800 Subject: [PATCH 5/7] Add question --- rfcs/2025-shadow-dom-support.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rfcs/2025-shadow-dom-support.md b/rfcs/2025-shadow-dom-support.md index c8a8ec3d24b..f323054ae77 100644 --- a/rfcs/2025-shadow-dom-support.md +++ b/rfcs/2025-shadow-dom-support.md @@ -113,6 +113,7 @@ Unknown, haven't done research here yet. * How to actually define the limitations of our support? See Introduction, it's missing a final sentence with this information. * User Event and JSDOM do not have good shadow DOM support, and user event has not been accepting PRs, can we count of them when testing? https://github.com/testing-library/user-event/issues/1026 +* Will we apply any changes to V3? or only RAC and S2 moving forward? ## Help Needed From 6356afdbdf83230e5d7363d591fb47cb2d182bcb Mon Sep 17 00:00:00 2001 From: Robert Snow Date: Wed, 18 Mar 2026 10:28:36 +1100 Subject: [PATCH 6/7] Update RFC --- rfcs/2025-shadow-dom-support.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/rfcs/2025-shadow-dom-support.md b/rfcs/2025-shadow-dom-support.md index f323054ae77..53edf9278d7 100644 --- a/rfcs/2025-shadow-dom-support.md +++ b/rfcs/2025-shadow-dom-support.md @@ -41,6 +41,8 @@ As mentioned earlier, there are proposed parts to this initiative: 1. Custom React Testing Library Renderer +**Resolution** I tried to do this, but it won't work for our current tests. User event doesn't have shadow dom support, we could use `shadow-dom-testing-library` but it has a different API (though similar) and is a fork, so could diverge more. In addition, many of our tests make assumptions, accidentally, about how they are rendered. It would amount to rewriting most tests. As such, I think we create net-new ones, and don't try to shoe horn our current tests into this. + Much like our custom renderer to test React.StrictMode, we should create a custom React Testing Library renderer for our unit tests which can wrap each test's rendered dom in a shadow root. This will give us a baseline to develop against and it will also hold us accountable in any future changes without needing to write many specific tests. In the worst case, should we pull the plug on this, it will also make it easy to remove the tests. @@ -52,13 +54,24 @@ It unfortunately appears that we cannot just keep our existing tests, there's ju 2. Avoid DOM Traversal/Manipulation +**UPDATE** We have a PR to hopefully remove at least the need for focus marshalling in a containing focus scope, this would solve many issues as we could let the browser handle tab navigation in the default manner. That work is here: https://github.com/adobe/react-spectrum/pull/8796 + This is most prominent in Focus Scope where we traverse the DOM in order to assign focus, such as in Collections, and contain focus such as in Dialogs. One proposal to avoid this traversal is to create focusable sentinels in order to trap focus. This will have the side benefit of theoretically working with closed root shadow doms as well, such as the aforementioned native video players inside a Dialog. -3. In-team Education +3. Avoid global listeners + +Many of our hooks and components attach global listeners or global observers. These cannot see inside a shadow root. As a result, they may miss events or respond to an event incorrectly. For example, focus and blur events do not bubble past a shadow root unless focus is leaving or entering the boundary entirely. Observers watching child lists cannot see when children are added within a shadow root. + +We can extend observers to each shadow root if we are watching children and we've done so in ariaHideOutside. We've also added event listeners to shadow roots such as in FocusScope. There are likely other places this happens, but they require unique fixes, not a one size fits all. + + +4. In-team Education + +**UPDATE** We've merged a number of PRs with eslint rules that either outright fix the usage or recommend a best approach. All the rules outlined below are done plus some extras. Writing code that can handle the prescence of Shadow DOM can be tricky. As a result, there will be a learning curve for the team. @@ -82,7 +95,7 @@ e.currentTarget.contains(e.target) nodeContains(e.currentTarget, getEventTarget(e.nativeEvent)) ``` -4. Communication +5. Communication We have our current Shadow DOM support gated behind a feature flag to both signal that it is experimental as well as to protect users who are not using any Shadow DOM from possible performance hits. @@ -107,24 +120,25 @@ This is a backwards compatible change, we should just be extending functionality ## Alternatives -Unknown, haven't done research here yet. +Not applicable. ## Open Questions * How to actually define the limitations of our support? See Introduction, it's missing a final sentence with this information. -* User Event and JSDOM do not have good shadow DOM support, and user event has not been accepting PRs, can we count of them when testing? https://github.com/testing-library/user-event/issues/1026 +* User Event and JSDOM do not have good shadow DOM support, and user event has not been accepting PRs, can we count on them when testing? https://github.com/testing-library/user-event/issues/1026 * Will we apply any changes to V3? or only RAC and S2 moving forward? ## Help Needed The biggest help we can receive is tests, either in the form of unit tests or in the form of examples of real life applications/setups that we can turn into unit tests. The more tests we have, the less likely we will break anything moving forward after the initial effort is complete. -"shadow-dom-testing-library" can be used in place of functions from test-library. +`shadow-dom-testing-library` can be used in place of functions from test-library. ## Frequently Asked Questions * How much can we count on contributor support? will the code just rot after the initial push? * We should expect that we'll take ownership of any code that comes in, we should not count on external support. + * eslint is a good way to counteract this * Is there any benefit to our selves or other people not using Shadow DOM? * Yes, see some of the issues listed above, specifically native video players breaking FocusScopes in Dialogs. * Users may be unaware that they are using Shadow DOM as it may be an implementation detail of a 3rd party component. @@ -133,3 +147,5 @@ The biggest help we can receive is tests, either in the form of unit tests or in ## Related Discussions Original issue: [feat: Focus Management within ShadowDOM](https://github.com/adobe/react-spectrum/pull/6046). Many of the ideas discussed in this RFC are from conversations around this PR. + +Known remaining issues after large push https://github.com/orgs/adobe/projects/19/views/32?pane=issue&itemId=157309187 From 4f664d1c64dd3be1d30e9c540df5c45a85226847 Mon Sep 17 00:00:00 2001 From: Robert Snow Date: Wed, 18 Mar 2026 12:04:47 +1100 Subject: [PATCH 7/7] Try to change the tone to be more formal --- rfcs/2025-shadow-dom-support.md | 134 +++++++++++++------------------- 1 file changed, 52 insertions(+), 82 deletions(-) diff --git a/rfcs/2025-shadow-dom-support.md b/rfcs/2025-shadow-dom-support.md index 53edf9278d7..4a4c957e648 100644 --- a/rfcs/2025-shadow-dom-support.md +++ b/rfcs/2025-shadow-dom-support.md @@ -8,94 +8,73 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. --> - Start Date: 2025/1/28 -- RFC PR: exploration PRs: https://github.com/adobe/react-spectrum/pull/6046 +- RFC PR: Exploration PRs: https://github.com/adobe/react-spectrum/pull/6046 - Authors: Rob Snow # Improving React Aria Shadow DOM Support ## Summary -This RFC outlines a plan for progressive enhancement of Shadow DOM support in React Aria, React Aria Components, and React Spectrum S2. -Shadow DOM support can mean many things; open root vs closed root, single containing shadow root, or multitude of individual components. -We can improve across all of these. - +This RFC outlines a plan for progressive enhancement of Shadow DOM support across React Aria, React Aria Components, and React Spectrum S2. “Shadow DOM support” can mean many things: it spans open versus closed shadow roots, a single containing shadow root versus many per-component roots, and interaction with third-party encapsulation. The work described here is intended to improve behavior across these scenarios where feasible. ## Motivation -As Shadow DOM is used by more libraries and applications, users have encountered friction trying to adopt or use our libraries. This might be using another 3rd party library that wants to prevent outside styles from affecting their components. It might be the usage of web components. Or, it might be just using native controls such as the `video` tag. +As Shadow DOM is used by more libraries and applications, users have encountered friction trying to adopt or use our libraries. Reported issues include incorrect focus management, broken overlay and press behavior, and `ariaHideOutside` misbehavior when trees cross shadow boundaries. -Some examples of these are: -- [Dialog's focus management and work with 3rd party dialogs](https://github.com/adobe/react-spectrum/issues/5314) -- [Video Controls are not respected when using FocusScope](https://github.com/adobe/react-spectrum/issues/6729) -- [FocusScope not working when used inside shadowRoot](https://github.com/adobe/react-spectrum/issues/1472) -- [ariaHideOutside incorrect behavior inside shadow DOM.](https://github.com/adobe/react-spectrum/issues/6133) -- [usePress is not work in shadowRoot](https://github.com/adobe/react-spectrum/issues/2040) -- [useOverlay Click Outside in Shadow-DOM context](https://github.com/adobe/react-spectrum/issues/3970) +Representative issues: -We have also had a contribution to solve some of the issues. While this is useful, we would like it to feel less hacky and more importantly, we'd like to incorporate the support into our daily lives in as easy as way as possible. +- [Dialog focus management with third-party dialogs](https://github.com/adobe/react-spectrum/issues/5314) +- [Video controls and FocusScope](https://github.com/adobe/react-spectrum/issues/6729) +- [FocusScope inside `shadowRoot`](https://github.com/adobe/react-spectrum/issues/1472) +- [`ariaHideOutside` inside shadow DOM](https://github.com/adobe/react-spectrum/issues/6133) +- [`usePress` in `shadowRoot`](https://github.com/adobe/react-spectrum/issues/2040) +- [`useOverlay` click-outside in shadow DOM](https://github.com/adobe/react-spectrum/issues/3970) +Prior contributions have addressed subsets of these problems. We are looking for a solution that is maintainable, integrates cleanly with day-to-day development, and reduces reliance on ad hoc workarounds. ## Detailed Design As mentioned earlier, there are proposed parts to this initiative: -1. Custom React Testing Library Renderer - -**Resolution** I tried to do this, but it won't work for our current tests. User event doesn't have shadow dom support, we could use `shadow-dom-testing-library` but it has a different API (though similar) and is a fork, so could diverge more. In addition, many of our tests make assumptions, accidentally, about how they are rendered. It would amount to rewriting most tests. As such, I think we create net-new ones, and don't try to shoe horn our current tests into this. - -Much like our custom renderer to test React.StrictMode, we should create a custom React Testing Library renderer for our unit tests which can wrap each test's rendered dom in a shadow root. - -This will give us a baseline to develop against and it will also hold us accountable in any future changes without needing to write many specific tests. In the worst case, should we pull the plug on this, it will also make it easy to remove the tests. +### 1. Testing -I expect many tests will fail in the beginning. We make use of a lot of DOM API's and have not generally thought of the ShadowDOM while developing. +Tests will be net new. This is the biggest body of needed work. Without it, we don't know what we are supporting and we can't catch regressions. -A first go at it can be found here: https://github.com/adobe/react-spectrum/compare/get-tests-running-in-shadowdom?expand=1 -It unfortunately appears that we cannot just keep our existing tests, there's just too many differences. +**Tooling:** [`shadow-dom-testing-library`](https://github.com/KonnorRogers/shadow-dom-testing-library) may supplement or replace certain Testing Library utilities where shadow-aware queries are required. -2. Avoid DOM Traversal/Manipulation +**Custom renderer (alternative considered):** A React Testing Library renderer that wraps each test’s output in a shadow root—analogous to the existing StrictMode test setup—would provide a strong baseline. An initial exploration ([comparison branch](https://github.com/adobe/react-spectrum/compare/get-tests-running-in-shadowdom?expand=1)) indicated that wholesale migration of existing tests is impractical due to API assumptions and volume of failures. -**UPDATE** We have a PR to hopefully remove at least the need for focus marshalling in a containing focus scope, this would solve many issues as we could let the browser handle tab navigation in the default manner. That work is here: https://github.com/adobe/react-spectrum/pull/8796 +### 2. Avoid DOM Traversal/Manipulation -This is most prominent in Focus Scope where we traverse the DOM in order to assign focus, such as in Collections, and contain focus such as in Dialogs. +FocusScope and collection-related code currently traverse the DOM to assign and contain focus (e.g., dialogs, roving tabindex). Prefer deferring tab order to the browser where possible. -One proposal to avoid this traversal is to create focusable sentinels in order to trap focus. +A promising direction is to reason about stacking context / focus escape so that focus is intercepted only when it would leave the intended scope for an invalid destination. This stops watching the Tab key completely. Exploration exists in [PR #8796](https://github.com/adobe/react-spectrum/pull/8796). -This will have the side benefit of theoretically working with closed root shadow doms as well, such as the aforementioned native video players inside a Dialog. +**Alternative (sentinel nodes):** Placing focusable sentinels to trap focus could reduce custom traversal and may help with closed shadow roots (e.g., native video controls inside a dialog). This remains a candidate if the stacking-context approach is insufficient. -3. Avoid global listeners +### 3. Scoped listeners and observers -Many of our hooks and components attach global listeners or global observers. These cannot see inside a shadow root. As a result, they may miss events or respond to an event incorrectly. For example, focus and blur events do not bubble past a shadow root unless focus is leaving or entering the boundary entirely. Observers watching child lists cannot see when children are added within a shadow root. +Global listeners and `MutationObserver` (and similar) do not observe inside shadow roots by default. Consequences include missed or misattributed events: for example, focus and blur do not bubble across shadow boundaries except when focus enters or exits the root; child-list observers do not see mutations inside descendant shadow trees. -We can extend observers to each shadow root if we are watching children and we've done so in ariaHideOutside. We've also added event listeners to shadow roots such as in FocusScope. There are likely other places this happens, but they require unique fixes, not a one size fits all. +Attach listeners and extend observers to relevant shadow roots where the implementation currently assumes a single document subtree. Fixes are expected to be contextual rather than one universal abstraction. - -4. In-team Education - -**UPDATE** We've merged a number of PRs with eslint rules that either outright fix the usage or recommend a best approach. All the rules outlined below are done plus some extras. +### 4. In-team Education Writing code that can handle the prescence of Shadow DOM can be tricky. As a result, there will be a learning curve for the team. -Some work has already been done to write utilities to hide away some of this complexity. We will also want to write lint rules to help us automatically catch as many common situations as possible. Some examples of these are: - -```jsx -document.activeElement -<-> -getActiveElement(); +ShadowDOM safe code requires discipline. Two mechanisms are proposed: +- **Shared utilities** encapsulating shadow-aware behavior (e.g., active element, event target, tree walking, containment checks). +- **Lint rules** to flag unsafe patterns and suggest utilities. -e.target -<-> -getTargetElement(e); +Illustrative mappings: +| Unsafe / naive pattern | Preferred utility-oriented pattern | +|------------------------|-----------------------------------| +| `document.activeElement` | `getActiveElement()` (shadow-aware) | +| `e.target` | `getTargetElement(e)` | +| `document.createTreeWalker(...)` | `createShadowTreeWalker(...)` | +| `e.currentTarget.contains(e.target)` | `nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))` | -let walker = document.createTreeWalker(...) -<-> -let walker = createShadowTreeWalker(...) - -e.currentTarget.contains(e.target) -<-> -nodeContains(e.currentTarget, getEventTarget(e.nativeEvent)) -``` - -5. Communication +### 5. Communication We have our current Shadow DOM support gated behind a feature flag to both signal that it is experimental as well as to protect users who are not using any Shadow DOM from possible performance hits. @@ -107,45 +86,36 @@ For example, some of our support may require open root Shadow DOMs. Or someone u ## Drawbacks -One concern is on-going support. Our current use cases do not call for Shadow DOM support, so knowledge on the team is currently thin. It will take time for people to ramp up on skills. Not only that, but it isn't part of our weekly testing and we have no plans for it to be at this time, which means we must rely on unit tests as much as possible. - -In addition, contributions may occur for a little while until those teams have their needs met. However, it should be assumed that there will be further work to complete the goals as outlined here. +One concern is on-going support. Internal use cases do not heavily exercise Shadow DOM today; team expertise is limited. Reliance on automated tests is necessary because Shadow DOM is not a focus for the team. -Another concern is that the current approach is, for lack of a better word, hacky. This is because we are accessing and manipulating the Shadow DOM in ways that it wasn't really intended. If we were to rewrite our library today, there are other ways we'd solve these issues which would respect more of the concept of the Shadow DOM and its purpose. What we do here and now may complicate a future where we have different APIs to support this vision of what support would ideally look like. +External contributions may taper once immediate needs are met; the maintainers should assume responsibility for completing the goals in this RFC. +Current mitigations interact with shadow trees in ways that are not always aligned with encapsulation as originally envisioned. Future API redesigns might prefer different models; present choices could complicate migration. -## Backwards Compatibility Analysis +## Backwards compatibility This is a backwards compatible change, we should just be extending functionality, not breaking any of it. -## Alternatives - -Not applicable. - -## Open Questions - -* How to actually define the limitations of our support? See Introduction, it's missing a final sentence with this information. -* User Event and JSDOM do not have good shadow DOM support, and user event has not been accepting PRs, can we count on them when testing? https://github.com/testing-library/user-event/issues/1026 -* Will we apply any changes to V3? or only RAC and S2 moving forward? -## Help Needed +## Open questions -The biggest help we can receive is tests, either in the form of unit tests or in the form of examples of real life applications/setups that we can turn into unit tests. The more tests we have, the less likely we will break anything moving forward after the initial effort is complete. +1. **Support matrix:** How should supported versus unsupported Shadow DOM configurations be defined and documented (open vs. closed, single vs. multiple roots)? +2. **Test environment:** `user-event` and JSDOM have known shadow DOM gaps ([e.g. user-event #1026](https://github.com/testing-library/user-event/issues/1026)); what is the long-term testing strategy if upstream fixes are slow? +3. **Scope:** Should changes apply to React Spectrum v3, or only to React Aria Components and S2 going forward? -`shadow-dom-testing-library` can be used in place of functions from test-library. +## Request for community input -## Frequently Asked Questions +The highest-value contributions are tests: unit tests or minimal reproductions derived from real applications that can be turned into permanent fixtures. Additional coverage directly reduces regression risk after the initial implementation phase. -* How much can we count on contributor support? will the code just rot after the initial push? - * We should expect that we'll take ownership of any code that comes in, we should not count on external support. - * eslint is a good way to counteract this -* Is there any benefit to our selves or other people not using Shadow DOM? - * Yes, see some of the issues listed above, specifically native video players breaking FocusScopes in Dialogs. - * Users may be unaware that they are using Shadow DOM as it may be an implementation detail of a 3rd party component. +## Frequently asked questions +**How much can the project rely on contributor maintenance after merge?** +Maintainers should treat contributed code as owned by the project. ESLint and shared utilities reduce the cost of keeping patterns consistent. -## Related Discussions +**Do users who never use Shadow DOM benefit?** +Yes. Several reported bugs involve shadow trees introduced by third parties or by native elements (e.g., video controls inside dialogs). Consumers may be unaware that shadow DOM is in play. -Original issue: [feat: Focus Management within ShadowDOM](https://github.com/adobe/react-spectrum/pull/6046). Many of the ideas discussed in this RFC are from conversations around this PR. +## Related discussions -Known remaining issues after large push https://github.com/orgs/adobe/projects/19/views/32?pane=issue&itemId=157309187 +- Original exploration: [feat: Focus Management within ShadowDOM](https://github.com/adobe/react-spectrum/pull/6046) +- [Known remaining issues (project board)](https://github.com/orgs/adobe/projects/19/views/32?pane=issue&itemId=157309187)