Description
Is there an existing issue for this?
- I have searched the existing issues
Describe the issue
I placed a react-native-web component in a shadow root, and the styles were wrong. Specifically, these components have the correct class names, but the style sheet seems to have not been loaded.
<Button color='red' title='Test'/>
looks like

In the chrome debugger, its DOM element looks like:
<button role="button" class="css-view-175oi2r r-transitionProperty-1i6wzkk r-userSelect-lrvibr r-cursor-1loqt21 r-touchAction-1otgn73 r-borderRadius-1jkafct" type="button" style="background-color: rgb(255, 0, 0); transition-duration: 0s;"><div dir="auto" class="css-text-146c3p1 r-color-jwli3a r-fontWeight-majxgm r-padding-edyy15 r-textAlign-q4m81j r-textTransform-tsynxw">Test</div></button>
however Chrome can't resolve any of these classes - only the host's stylesheet is being applied.
Expected behavior
Steps to reproduce
-
Quickstart with Create React App
-
Obtain a ShadowDom react component (either react-shadow or roll your own).
I had the same issue with react-shadow (which was presumably written by someone who knows what they're doing!), but for the sake of this issue I'm using the following wrapper as it's a simpler repro - please let me know if I'm making a glaring mistake as I'm not an experienced web developer.
function ShadowDomWrapper({children}) {
const shadowHost = useRef();
const reactRoot = useRef();
useEffect(() => {
if (shadowHost.current) {
if (!reactRoot.current) {
const shadowRoot = shadowHost.current.attachShadow({mode: "open"});
reactRoot.current = createRoot(shadowRoot);
}
reactRoot.current.render(children);
}
});
return <div ref={shadowHost}></div>
}
- Render a react-native-web component in and outside the shadow root to see how they differ:
function App() {
return (
<div className="App">
<header className="App-header">
...other stuff...
<Button color='red' title='Test'/>
<ShadowDomWrapper>
<Button color='red' title='Test'/>
</ShadowDomWrapper>
...other stuff...
Test case
https://codesandbox.io/p/sandbox/react-native-web-shadow-dom-repro-5gy7vw
Additional comments
I saw some related issues, e.g. #1517 and it seems support for the shadow DOM was added with 0.18. It's very possible that I'm just missing a step, but there's no documentation for how to get this working! How does react-native-web provide the stylesheets to the root node of the app? How do I do this for the shadow root?
Why am I trying to do this?
I have a component library written in react-native, for cross-platform compatibility between our react-native app and our next.js app. When rendering components from the library in next.js, they are suddenly affected by CSS stylesheets from the next.js app, which can break the styling. The components are already styled with the style
prop - I do not need external styling so I want to isolate them from the styling added by the website, hence the use of shadow dom. However, I seem to be losing the RNW styles too!
Activity
necolas commentedon Nov 28, 2023
You can't use
createRoot
directly fromreact-dom/client
, as that doesn't allow the library to insert the style sheet into the shadow DOM. You have to use therender
function from RNWhttps://codesandbox.io/p/sandbox/react-native-web-shadow-dom-repro-forked-xf696q
However, you shouldn't have to reach into the internals to do this, but the exported
render
wasn't updated to use React 18'screateRoot
. I also encountered a couple of other bugs related to this functionality that I'll fix in a patch. I'll post it here for you to try once it's up. Thanks![fix] 'render' export uses ReactDOM.createRoot
necolas commentedon Nov 29, 2023
The PR related to this issue is #2615
You can install
0.0.0-dee1467a3
to try it out - it's just the PR stacked on top of 0.19.9. Here's an updated code example:https://codesandbox.io/p/sandbox/react-native-web-shadow-dom-repro-forked-86y3vd
Please try it out in your app, and me know if you encounter any issues, including any issues outside of the shadow DOM. If everything looks good after you've audited your stuff, I can merge the PR and cut a main-line release. Thanks!
chriscoomber commentedon Nov 30, 2023
Yep, that seems to work for me! Thanks a lot.
I have one question though - it looks like if I define react Contexts outside of the ShadowDomWrapper, they're not available within. Is that to be expected? Is there any way to work around that (aside from re-declaring them all manually in ShadowDomWrapper)?
Example: https://codesandbox.io/p/sandbox/react-native-web-shadow-dom-repro-forked-yn52hj
necolas commentedon Nov 30, 2023
Yeah that's expected because we're creating a completely new React root. If you want to avoid consuming and re-providing contexts, I think you could render the components into a portal:
https://codesandbox.io/p/sandbox/react-native-web-shadow-dom-repro-forked-cfddm3
There will be other differences between
render
andcreatePortal
implementations, e.g., getting event targets propagating from within the shadow dom. React doesn't really support this use case very well, and events-from-within portals are a bit funky.