Skip to content

Commit 4f664d1

Browse files
committed
Try to change the tone to be more formal
1 parent 5c4d605 commit 4f664d1

File tree

1 file changed

+52
-82
lines changed

1 file changed

+52
-82
lines changed

rfcs/2025-shadow-dom-support.md

Lines changed: 52 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -8,94 +8,73 @@ OF ANY KIND, either express or implied. See the License for the specific languag
88
governing permissions and limitations under the License. -->
99

1010
- Start Date: 2025/1/28
11-
- RFC PR: exploration PRs: https://github.com/adobe/react-spectrum/pull/6046
11+
- RFC PR: Exploration PRs: https://github.com/adobe/react-spectrum/pull/6046
1212
- Authors: Rob Snow
1313

1414
# Improving React Aria Shadow DOM Support
1515

1616
## Summary
1717

18-
This RFC outlines a plan for progressive enhancement of Shadow DOM support in React Aria, React Aria Components, and React Spectrum S2.
19-
Shadow DOM support can mean many things; open root vs closed root, single containing shadow root, or multitude of individual components.
20-
We can improve across all of these.
21-
18+
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.
2219

2320
## Motivation
2421

25-
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.
22+
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.
2623

27-
Some examples of these are:
28-
- [Dialog's focus management and work with 3rd party dialogs](https://github.com/adobe/react-spectrum/issues/5314)
29-
- [Video Controls are not respected when using FocusScope](https://github.com/adobe/react-spectrum/issues/6729)
30-
- [FocusScope not working when used inside shadowRoot](https://github.com/adobe/react-spectrum/issues/1472)
31-
- [ariaHideOutside incorrect behavior inside shadow DOM.](https://github.com/adobe/react-spectrum/issues/6133)
32-
- [usePress is not work in shadowRoot](https://github.com/adobe/react-spectrum/issues/2040)
33-
- [useOverlay Click Outside in Shadow-DOM context](https://github.com/adobe/react-spectrum/issues/3970)
24+
Representative issues:
3425

35-
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.
26+
- [Dialog focus management with third-party dialogs](https://github.com/adobe/react-spectrum/issues/5314)
27+
- [Video controls and FocusScope](https://github.com/adobe/react-spectrum/issues/6729)
28+
- [FocusScope inside `shadowRoot`](https://github.com/adobe/react-spectrum/issues/1472)
29+
- [`ariaHideOutside` inside shadow DOM](https://github.com/adobe/react-spectrum/issues/6133)
30+
- [`usePress` in `shadowRoot`](https://github.com/adobe/react-spectrum/issues/2040)
31+
- [`useOverlay` click-outside in shadow DOM](https://github.com/adobe/react-spectrum/issues/3970)
3632

33+
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.
3734

3835
## Detailed Design
3936

4037
As mentioned earlier, there are proposed parts to this initiative:
4138

42-
1. Custom React Testing Library Renderer
43-
44-
**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.
45-
46-
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-
48-
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.
39+
### 1. Testing
4940

50-
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.
41+
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.
5142

52-
A first go at it can be found here: https://github.com/adobe/react-spectrum/compare/get-tests-running-in-shadowdom?expand=1
53-
It unfortunately appears that we cannot just keep our existing tests, there's just too many differences.
43+
**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.
5444

55-
2. Avoid DOM Traversal/Manipulation
45+
**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.
5646

57-
**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
47+
### 2. Avoid DOM Traversal/Manipulation
5848

59-
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.
49+
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.
6050

61-
One proposal to avoid this traversal is to create focusable sentinels in order to trap focus.
51+
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).
6252

63-
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.
53+
**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.
6454

65-
3. Avoid global listeners
55+
### 3. Scoped listeners and observers
6656

67-
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.
57+
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.
6858

69-
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.
59+
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.
7060

71-
72-
4. In-team Education
73-
74-
**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.
61+
### 4. In-team Education
7562

7663
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.
7764

78-
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:
79-
80-
```jsx
81-
document.activeElement
82-
<->
83-
getActiveElement();
65+
ShadowDOM safe code requires discipline. Two mechanisms are proposed:
66+
- **Shared utilities** encapsulating shadow-aware behavior (e.g., active element, event target, tree walking, containment checks).
67+
- **Lint rules** to flag unsafe patterns and suggest utilities.
8468

85-
e.target
86-
<->
87-
getTargetElement(e);
69+
Illustrative mappings:
70+
| Unsafe / naive pattern | Preferred utility-oriented pattern |
71+
|------------------------|-----------------------------------|
72+
| `document.activeElement` | `getActiveElement()` (shadow-aware) |
73+
| `e.target` | `getTargetElement(e)` |
74+
| `document.createTreeWalker(...)` | `createShadowTreeWalker(...)` |
75+
| `e.currentTarget.contains(e.target)` | `nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))` |
8876

89-
let walker = document.createTreeWalker(...)
90-
<->
91-
let walker = createShadowTreeWalker(...)
92-
93-
e.currentTarget.contains(e.target)
94-
<->
95-
nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))
96-
```
97-
98-
5. Communication
77+
### 5. Communication
9978

10079
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.
10180

@@ -107,45 +86,36 @@ For example, some of our support may require open root Shadow DOMs. Or someone u
10786

10887
## Drawbacks
10988

110-
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.
111-
112-
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.
89+
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.
11390

114-
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.
91+
External contributions may taper once immediate needs are met; the maintainers should assume responsibility for completing the goals in this RFC.
11592

93+
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.
11694

117-
## Backwards Compatibility Analysis
95+
## Backwards compatibility
11896

11997
This is a backwards compatible change, we should just be extending functionality, not breaking any of it.
12098

121-
## Alternatives
122-
123-
Not applicable.
124-
125-
## Open Questions
126-
127-
* How to actually define the limitations of our support? See Introduction, it's missing a final sentence with this information.
128-
* 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
129-
* Will we apply any changes to V3? or only RAC and S2 moving forward?
13099

131-
## Help Needed
100+
## Open questions
132101

133-
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.
102+
1. **Support matrix:** How should supported versus unsupported Shadow DOM configurations be defined and documented (open vs. closed, single vs. multiple roots)?
103+
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?
104+
3. **Scope:** Should changes apply to React Spectrum v3, or only to React Aria Components and S2 going forward?
134105

135-
`shadow-dom-testing-library` can be used in place of functions from test-library.
106+
## Request for community input
136107

137-
## Frequently Asked Questions
108+
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.
138109

139-
* How much can we count on contributor support? will the code just rot after the initial push?
140-
* We should expect that we'll take ownership of any code that comes in, we should not count on external support.
141-
* eslint is a good way to counteract this
142-
* Is there any benefit to our selves or other people not using Shadow DOM?
143-
* Yes, see some of the issues listed above, specifically native video players breaking FocusScopes in Dialogs.
144-
* Users may be unaware that they are using Shadow DOM as it may be an implementation detail of a 3rd party component.
110+
## Frequently asked questions
145111

112+
**How much can the project rely on contributor maintenance after merge?**
113+
Maintainers should treat contributed code as owned by the project. ESLint and shared utilities reduce the cost of keeping patterns consistent.
146114

147-
## Related Discussions
115+
**Do users who never use Shadow DOM benefit?**
116+
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.
148117

149-
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.
118+
## Related discussions
150119

151-
Known remaining issues after large push https://github.com/orgs/adobe/projects/19/views/32?pane=issue&itemId=157309187
120+
- Original exploration: [feat: Focus Management within ShadowDOM](https://github.com/adobe/react-spectrum/pull/6046)
121+
- [Known remaining issues (project board)](https://github.com/orgs/adobe/projects/19/views/32?pane=issue&itemId=157309187)

0 commit comments

Comments
 (0)