You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I would like to have the ability to use declarative shadow DOM (without custom elements) within Solid components just to get style scoping. It doesn't work and I get the error Uncaught TypeError: Cannot read properties of null (reading 'nextSibling')
exportdefaultfunctionShadowTest(){return(<div><templateshadowrootmode="open"><p>This is in the shadow DOM</p><slot><p>This is in the light DOM</p><button>Light button</button></slot><button>Shadow button</button></template></div>);}
Expected behavior
It should render a shadow tree.
Screenshots or Videos
No response
Platform
OS: all
Browser: Chrome + Safari (Firefox hasn't shipped support for it yet)
constd=document.createElement("div")d.innerHTML=`<div> <template shadowrootmode="open"> <p>This is in the shadow DOM</p> <slot> <p>This is in the light DOM</p> <button>Light button</button> </slot> <button>Shadow button</button> </template> </div>`
And I got:
Found declarative shadowrootmode attribute on a template, but declarative Shadow DOM has not been enabled by includeShadowRoots.
I was just trying to figure out what the parsing would look like for this. I think it might start with identifying with template elements we need to walk into .content before continuing into firstChild/nextSibling. That would probably fix the error assuming that it stays in the DOM like that and doesn't get swallowed. Does the template element actually have active nodes in this scenario? Most template elements contain only inert nodes.
A simple workaround is to make a <ShadowRoot> component, and then create the shadow root with JavaScript in there, then finally pass props.children along.
Something like the following (I wrote it quickly, didn't test it, but it shows idea):
functionShadowRoot(props){letdivonMount(()=>{constroot=div.attachShadow(...)render(props.children,root)// or similar})return<divref={div}></div>}functionUsageExample(){return<ShadowRoot><div>hello<div><style>{` /* scoped style */ div { background: blue } `}</style></ShadowRoot>}
But this is not compatible with SSR, it creates a new root, and contexts won't automatically flow into the new root, etc. Other than that it has worked great in some places where I used it.
Declarative shadow roots will be, and that'll be really nice! Every framework that doesn't have scoped styles out of the box is gonna now have scoped styles out of the box, with the same sorts of patterns as custom elements.
Revisiting this. innerHTML doesn't play nicely with declarative shadow DOM. When you inject HTML containing <template shadowrootmode="open"> into the page with innerHTML, it remains just a template, whereas it's meant to instantly become shadow DOM. innerHTML doesn't understand shadowrootmode.
There is a new setHTMLUnsafe method (supported in all browsers) and getHTML method (supported only in Chrome 125) that together act as a declarative shadow DOM friendly alternative to innerHTML.
This would epic to have it working. Combine it with solidjs/solid-router#459 and we can get awesome power of Solid.js together with Server Actions and Server Rendering. ❤️
/** * A declarative shadow root component * * Hooks into SolidJS' Portal's `useShadow` prop * to handle shadow DOM and the component lifecycle */constShadowRoot: ParentComponent=(props)=>{letdiv: HTMLDivElement;return(<divref={div!}><Portalmount={div!}useShadow={true}>{props.children}</Portal></div>);};
Reacted by Supertiger, Jose Vargas and Guyutongxue
I'm not really clear what you all are talking about in regards to portals here. Yes portals in 1.0 can use the shadow DOM, but declarative shadow DOM is a very different thing.
I'm not really clear what you all are talking about in regards to portals here. Yes portals in 1.0 can use the shadow DOM, but declarative shadow DOM is a very different thing.
The Portals are simply a workaround for Declarative Shadow DOM (DSD) not working, where Portal imperatively creates the shadow root while the user writes declarative JSX.
The intent is that we can use shadow DOM in a Solid component by writing declarative markup (obviously we can do it imperatively and abstract it as in my example), but Portals just happen to be an existing API that can be used declaratively (but doing so is not compatible with SSR).
Native support for <template shadowrootmode>can be compatible with SSR because the server simply sends the markup to the client. Solid would need to be aware of it to take it into consideration during hydration.
I don't think users care if DSD actually works, what they want is simply to use Shadow DOM declaratively and it also work with SSR. How Solid does that (DSD or not) is inconsequential to the need:
the feature can be an implementation detail behind a built-in <ShadowRoot> component,
or Portal shadow roots could have SSR support added to them,
or Solid could map <template shadowrootmode> directly in the JSX to imperative ShadowRoot-creating code without DSD,
or Solid could use native parsing (f.e. setHTMLUnsafe) to use native DSD
and only the end result would matter: support for native slots and native style scoping in Solid components, with or without SSR.
Activity
ryansolid commentedon Aug 1, 2023
As a test I just tried in Chrome:
And I got:
I was just trying to figure out what the parsing would look like for this. I think it might start with identifying with template elements we need to walk into
.contentbefore continuing intofirstChild/nextSibling. That would probably fix the error assuming that it stays in the DOM like that and doesn't get swallowed. Does the template element actually have active nodes in this scenario? Most template elements contain only inert nodes.o-t-w commentedon Aug 2, 2023
This section of this article might answer that
ivancuric commentedon Aug 3, 2023
This functionality is already present for Portals using the
useShadowprop.Is it possible to expand this functionality to a generic utility component, eg something like
<Shadow>?trusktr commentedon Aug 9, 2023
A simple workaround is to make a
<ShadowRoot>component, and then create the shadow root with JavaScript in there, then finally passprops.childrenalong.Something like the following (I wrote it quickly, didn't test it, but it shows idea):
But this is not compatible with SSR, it creates a new root, and contexts won't automatically flow into the new root, etc. Other than that it has worked great in some places where I used it.
Declarative shadow roots will be, and that'll be really nice! Every framework that doesn't have scoped styles out of the box is gonna now have scoped styles out of the box, with the same sorts of patterns as custom elements.
o-t-w commentedon May 8, 2024
Revisiting this.
innerHTMLdoesn't play nicely with declarative shadow DOM. When you inject HTML containing<template shadowrootmode="open">into the page withinnerHTML, it remains just a template, whereas it's meant to instantly become shadow DOM.innerHTMLdoesn't understandshadowrootmode.There is a new
setHTMLUnsafemethod (supported in all browsers) andgetHTMLmethod (supported only in Chrome 125) that together act as a declarative shadow DOM friendly alternative toinnerHTML.o-t-w commentedon May 8, 2024
The documentation for all of this is currently pretty terrible so I wrote about it here: https://fullystacked.net/innerhtml-alternatives/
BleedingDev commentedon Jun 3, 2024
This would epic to have it working. Combine it with solidjs/solid-router#459 and we can get awesome power of Solid.js together with Server Actions and Server Rendering. ❤️
ivancuric commentedon Jul 11, 2024
This seems to work well:
Sleepful commentedon Jun 7, 2025
@ivancuric worked for me!
Additionally, here is how you can add styles to the shadow root if you are using vite, this worked for me on solidStart:
Per:
A little janky at load time because ShadowRoot does not render on the server, obviously
It needs to call
getStyleslike that, using the css import directly on the client-side results in errors due to server-side depsryansolid commentedon Apr 14, 2026
I'm not really clear what you all are talking about in regards to portals here. Yes portals in 1.0 can use the shadow DOM, but declarative shadow DOM is a very different thing.
trusktr commentedon Oct 17, 2026
The Portals are simply a workaround for Declarative Shadow DOM (DSD) not working, where Portal imperatively creates the shadow root while the user writes declarative JSX.
The intent is that we can use shadow DOM in a Solid component by writing declarative markup (obviously we can do it imperatively and abstract it as in my example), but Portals just happen to be an existing API that can be used declaratively (but doing so is not compatible with SSR).
Native support for
<template shadowrootmode>can be compatible with SSR because the server simply sends the markup to the client. Solid would need to be aware of it to take it into consideration during hydration.I don't think users care if DSD actually works, what they want is simply to use Shadow DOM declaratively and it also work with SSR. How Solid does that (DSD or not) is inconsequential to the need:
<ShadowRoot>component,<template shadowrootmode>directly in the JSX to imperative ShadowRoot-creating code without DSD,and only the end result would matter: support for native slots and native style scoping in Solid components, with or without SSR.