Comment by dfabulich
15 hours ago
HTML elements can style themselves now using the @scope rule. (It's Baseline Newly Available.) Unlike the "style" attribute, @scope blocks can include @media and other @ rules. You can't get more self-contained than this.
<swim-lane>
<style>
@scope {
background: pink;
b {
background: lightblue
}
@media (max-width: 650px) {
/* Mobile responsive styles */
}
}
</style>
something <b>cool</b>
</swim-lane>
You can also extract them to a CSS file, instead.
@scope (swim-lane) { /* ... */ }
The reason approaches like this continue to draw crowds is that Web Components™ as a term is a confluence of the Custom Elements JS API and Shadow DOM.
Shadow DOM is awful. Nobody should be using it for anything, ever. (It's required for putting child-element "slots" in custom elements, and so nobody should use those, either.) Shadow DOM is like an iframe in your page; styles can't escape the shadow root and they can't get into the shadow root, either. IDs are scoped in shadow roots, too, so the aria-labelledby attribute can't get in or out, either.
@scope is the right abstraction: parent styles can cascade in, but the component's styles won't escape the element, giving you all of the (limited) performance advantages of Shadow DOM with none of the drawbacks.
Decoupling slots from shadow dom would make custom elements even better.
I love custom elements. For non React.js apps I use them to create islands of reactivity. With Vue each custom element becomes a mini app, and can be easily lazy loaded for example. Due to how Vue 3 works, it’s even easy to share state & routing between them when required.
They should really move the most worthwhile features of shadow dom into custom elements: slots and the template shadow-roots, and associated forms are actually nice.
It’s all the extra stuff, like styling issues, that make it a pain in the behind
There's really no way to decouple slots for shadow roots.
For slots to work you need a container for the slots that the slotted elements do not belong to, and whose slots are separated from other slot containers. Otherwise you can't make an unambiguous relationship between element and slot. This is why a shadow root is a separate tree.
Agreed. The way I explain it is: suppose you have a `<super-table>` element, and you have a child slot called, for example, `<super-row-header>`. Presumably you want to write some JS to transform the slotted content in some way, decorating each row with the header the user provided.
But, if you do that, what happens to the original `<super-row-header>` element that the user provided? Maybe you'd want to delete it…? But how can you tell the difference between the user removing the `<row-header>` and the custom element removing it in the course of its work?
What you'd need is for `<row-header>` to somehow exist and not exist at the same time. Which is to say, you'd have one version of the DOM (the Light DOM) where the slot element exists, and another version of the DOM (the Shadow DOM) where the `<row-header>` element doesn't exist, and the transformed content exists instead.
It's clever, I guess, but the drawbacks far outweigh the benefits.
Client-side components inherently require JS anyway, so just use your favorite JS framework. Frameworks can't really interoperate while preserving fine-grained reactivity (in fact, Shadow DOM makes that harder), so, just pick a framework and use it.
That's styling itself sure, but it's not self-evidently self-contained. Does every component emit those styles? Are they in the page stylesheet? How do they get loaded?
Counterpoint: Shadow DOM is great. People should be using it more. It's the only DOM primitive that allows for interoperable composition. Without it you're at the mercy of frameworks for being able to compose container components out of internal structure and external children.
https://2025.stateofhtml.com/en-US/features/web_components/
Sort by negative sentiment; Shadow DOM is at the top of the list, the most hated feature in Web Components. You can read the comments, too, and they're almost all negative, and 100% correct.
"Accessibility nightmare"
"always hard to comprehend, and it doesn't get easier with time"
"most components don't need it"
"The big issue is you need some better way to some better way to incorporate styling from outside the shadow dom"
> It's the only DOM primitive that allows for interoperable composition.
There is no DOM primitive that allows for interoperable composition with fine-grained reactivity. Your framework offers fine-grained reactivity (Virtual DOM for React/Preact, signals for Angular, runes for Svelte, etc.) and any component that contains another component has to coordinate with it.
As a result, you can only mix-and-match container components between frameworks with different reactivity workflows by giving up on fine-grained reactivity, blowing away the internals when you cross boundaries between frameworks. (And Shadow DOM makes it harder, not easier, to coordinate workflows between frameworks.)
Shadow DOM sucks at the only thing it's supposed to be for. Please, listen to the wisdom of the crowd here.
> There is no DOM primitive that allows for interoperable composition with fine-grained reactivity. Your framework offers fine-grained reactivity (Virtual DOM for React/Preact, signals for Angular, runes for Svelte, etc.) and any component that contains another component has to coordinate with it.
This just isn't true - composition and reactivity are completely orthogonal concerns.
Any reactivity system can manage DOM outside of the component, including nodes that are projected into slots. The component's internal DOM is managed by the component using whatever reactivity system it desires.
There are major applications built this way. They make have a React outer shell using vdom and Lit custom elements using lit-html for their shadow contents.
On top of those basics you can also have cross-shadow interoperable fine-grained reactivity with primitives like signals. You can pass signals around, down the tree, across subtrees, and have different reactivity systems use those signals to update the DOM.
2 replies →
I feel like it’s a niche feature that got way too much attention. In a past job, we maintained a widget customers could embed onto their page. How much trouble we had with parent styles creeping into our widget and ruining the layout! This would have been so much easier with shadow DOM effectively isolating it from the customer site; that is the only valid use case for it, I feel.
Yet, for actual web components, I entirely agree with you.
Yeah but most people don't need or want 'interoperable composition', they want sites with a consistent look-and-feel. Shadow DOM makes this much more difficult.
I haven't played with the Shadow Dom since Polymer one, but we had defaults and variables to address this that worked amazingly, and helped standardize it with other teams far better than other css things we had done at the time. It looks like that is still a thing - https://shadow-style.github.io/ - without which people injected things through the CMS that were not fun to deal with.