(Cover: illusion installation by Daihei Shibata)

When it comes to Web Components, I think one of the main reasons why there's not more widely adopted is caused by the lack of (good) communication that led the specification editors to develop a standard the way it's been developed, and the userland community. As a result, there are a lot of misconceptions about the reasons, the goals and the use cases that Web Components want to cover.

Even experienced developers and menthors could fall in the same trap, as we'll see later.

One of the most misunderstood pieces is definitely the Shadow DOM. This feature recreates a custom DOM structure entirely contained inside our component, possibly completely different from the one we'd expect from the nodes we inserted as descendants.

Adam Savage from Mythbusters saying

What comes with the Shadow DOM

This jarring behavior is also just a part of what Shadow DOM implies: it is, in fact, the heart of the encapsulation capabilities of Web Components. It also means the following:

  • Stylesheets defined inside the Shadow DOM can only affect elements inside the Shadow DOM; viceversa, stylesheets outside do not have effect on elements inside the Shadow DOM - except for some well-defined ways that allow to fine-tune this kind of style spilling.
  • Many DOM-selection and traversing API are blocked by shadow roots, e.g. document.querySelector('button') cannot return a button placed inside a Shadow DOM. This includes ARIA cross-root references, which could have an impact on accessibility.
  • Events can now be composed, which is necessary if you want them to escape the boundaries of the shadow root.
  • When replacing the descendant nodes with its own DOM structure, it's possible to reassign the custom element's children to a special element called , either declaratively or imperatively.

Shadow DOMs behave like a miniature DOM nested inside our custom elements and, as such, they pose coding challenges that could reward us with features that could be hardly achievable otherwise.

Shadow DOM is not for everything

So, why should we use a Shadow DOM inside our Web Components? Why should we torture ourselves with those challenges? We want our job as simple as possible, right?

Let's start saying that nobody says we should always use a Shadow DOM. We don't always need this level of encapsulation. But when we do, in my opinion it's a great solution.

For example, if I want to define an UI Kit for my/my company/third party applications, I'd like to have a consistent layout and appearance. I want its colors, its spacings, its decorative features to be fixed. I want their pieces and bits always be present in the places I intended them to be (its internal layout). And when I allow some external customization, I'd like to limit the impacts consumers may have. Otherwise, I'd allow consumers to break the underlying design system.

For example, imagine you want to create a classic "collapsible card" component , and suppose you don't want to use the Shadow DOM. Your card is going to have its own internal structure, so the first thing you could do is to manipulate the DOM via JavaScript in order to move around child nodes. When should we perform this operation? When the elements is attached to the page? When it first receives its children? What if the content is modified from external factors after that? How would you reconcile the changes with the component's intended structure?

UI Kits are probably the best example why we'd want some higher level of encapsulations. But that's not the end of it: encapsulation could be used also for some widely used components, like a checkout widget, or a cookie consent banner. I've used it for wrapping branches of legacy applications while migrating to another framework/newer version.

What about the rest? Would I always use a Shadow DOM when developing a full application with Web Components? Probably not. Because I wouldn't need strong encapsulation everywhere: it'd create unnecessary hurdles for little or no result. It's no use to define a shadow DOM for a component that comprise a whole page, for example.

So, what's the fuss about?

Those points are summarily examined in Chris Ferdinandi's article "The Shadow DOM is an antipattern" that was published last June 28th. In the article, Chris minimizes the advantages of Shadow DOMs and reduces the mentioned challenges as "disadvantages", concluding that using Shadow DOM is actually an "antipattern" that creates more problems that it solves.

But, as I said, I think he fell victim of the misconceptions I mentioned before. Let's see what I mean in particular:

[The Shadow DOM] prevents your global CSS from cascading into your Web Component HTML. This makes consistent styling significantly harder and more verbose.

This surprised me right off the bat, because preventing the cascade is exactly the goal of style encapsulation. You may want to encapsulate the styles to your component.

It's also unclear how that'd make "consistent styling significantly harder and more verbose". Let's remember that you absolutely can include more than one stylesheet in the Shadow DOM, so you can define a "global" list of core style rules that you can include in every component with a shadow root. So yeah, you can use your utility class .uppercase inside a Shadow DOM if you want to.

Second: when I said that external stylesheets don't affect the Shadow DOM, it's not completely true. In fact, all inherited CSS properties are passed through the shadow root. It's not like your shadow text will appear in Times New Roman in case of a missing definition inside, because font-family is indeed an inherited CSS property.

Other inherited properties are usually text-related, like font-size and color, but also visibility is inherited. And, above all, custom properties are normally inherited, allowing us to define a list of special properties that represent the set of design properties and values that can be shared among our components and even altered if needed. This is what's called a design token.

It (currently) requires you to generate your HTML with JavaScript. Declarative Shadow DOM is a newer feature that will fix this once better supported.

I have to underline that Declarative Shadow DOM is actually supported by all the major browsers (and can even be polyfilled), but even if we accept that it needs to be "better supported", the former statement is also misleading.

You can, in fact, limit yourself to grab a piece of (light) DOM, copy it (eventually) and attach it to your shadow root. Sure, you need to do that with JavaScript, but it can be as little as a single line. And lo, there's even a specific element for this exact case, which is also as old as the concept of "Web Components": the good ol' element, which is the base for Declarative Shadow DOM and the key for its polyfill, allowing us to just write:

this.shadowRoot.append(
  document.querySelector('#tpl').content.cloneNode(true)
);

You can saying that I'm sort of "playing with words", but since we do have access to the document, saying that we're "required" to use JavaScript to create HTML is flat-out incorrect.

From that point, I won't mention dynamic content, because light and Shadow DOM are clearly on par with this.

The encapsulation it provides also makes extending and customizing the code harder unless you build in lots of developer hooks.

That's probably my limitation, but I see little problem here. I just suppose he's not referring to "extending" in the OOP sense, as in:

const LibButton = customElements.get('lib-button');
class MyButton extends LibButton { ... }
customElements.define('my-button', MyButton);

because you can do that, although it's probably not the best thing to work with. Also because you cannot do that with native elements, e.g. class MyButton extends HTMLButtonElement {...}, unless you want to drop support for Safari. Native elements also are the "most closed" thing you could use to create an extension from.

That being said, why would you want to extend a custom element? If your goal is to be able to completely dismantle its layout and functionality... Well, you're not probably creating a UI Kit or other stuff that needs strong encapsulation anyway. So the point is moot.

Can you actually achieve “good enough” versions of the benefits without the Shadow DOM?

The question is followed by an interesting point:

The custom elements you use to create your Web Component provide a styling hook that can scope your styles to just the Web Component.

accordion-group button {
    /* Button styles just for this component */
}

I think I'm decently versed when it comes to CSS, and I applaud the people who show no fear of the cascade. But beware of the code above: because the accordion-group selector gives us an upper bound for our scope, but not a lower bound. This means that any button inside the accordion-group will be affected by the rule, even those that are not supposed to be part of the intrinsic structure of the element, but rather of its content.

Today, there's actually a far better solution for this, represented by the new @scope rule that, alas, is still not supported by Firefox (should be soon, though). That supports lower bound for our scopes.

Chris also suggests that:

When you need to deviate, add specificity or scope to an element/class/etc.

This is hardly a decent approach. If you feel the need to "add specificity" to your selectors, you're already on a slippery slope guarded by !important modifiers and several head-scratches. Be very careful when adding specificity. Not everything can be dismissed as

sounds like an issue with how the global stylesheet is designed.

In an encapsulated stylesheet? You don't even need to care: the cascade is much shorter and manageable. I don't even usually use classes in a shadow DOM.

And all in all, Chris' opinion sounds to me like it's limited by his own experience. It's not always a thing to have a handle on the global CSS. For example, I've created several UI kits in my career for many clients. That was just the design token and the components, that's it: my task didn't include developing the applications that used the kit. If I didn't use Shadow DOM, were I to dismiss every style problems that would have emerged with "well, that's a you problem #wontfix"?

Nope. You bet I've used the Shadow DOM: it saved me a lot of headaches.

The most misunderstood element

The post concludes with the following:

And while the element is really cool and I wish you could use it without the Shadow DOM, you absolutely can do the same thing without it.

It does require you to use the querySelector() method to grab elements, but it’s not anymore work than coding all of your HTML in JavaScript was in the first place.

That, in my opinion, is not a "good enough" version of . It's a very broken version of it.

Specifically, it doesn't react to changes to the set of children elements: it does its job once, and that's it. If you need to update the content of your custom element with new data, you're out of luck.

Sadly, Chris just doesn't explain how to deal with it. My naive assumption is that you either stay stuck with a broken look-alike, or if you try to recreate native slots using e.g. a MutationObserver, in the end, you'd be using more JavaScript. This is pretty surprising coming from someone so hostile to JavaScript.

And an anti-pattern to me.

Why we create "components" in the first place

The main goal to create a component is to have a reusable piece of markup that saves us to do the same things over and over again. But then you see his ideal usage of "Web Components":

method="post" action="/subscribe">
     for="email">Add your email to join the newsletter
     type="email" id="email" name="email">
    Join

This way, even if JavaScript is disabled, the form still works. Sure. But the cost is I saved almost nothing from my usual code grinding tasks.

The interesting part is that he apparently created this for NASA. I doubt NASA employees would complain if they can't turn JavaScript off, but little I know about the requirements. I can only say that, in my experience, it's only something I could be concerned with when building public web sites and apps.

As I've said in the past, relegating JavaScript to just progressive enhancements is narrow minded to me: there are so many patterns we commonly do with JavaScript, and while some of them could be done without it, others definitely cannot.

Accessibility patterns aside, there are multiple cases where you need to handle stuff with JavaScript on the frontend. It may be because you don't control the backend, so you need to elaborate the data on your own. Or because it makes no sense to do something in the backend (an interactive game for kids, for example; or an audio elaboration tool; or scanning a QR code...). Or because something can be generated on the client rather than doing a network round-trip to the server to create the same thing.

Everybody works with constraints. And not everything is a blog.