Scoping CSS Using Shadow DOM

A mechanism to stop overriding your CSS styles using Shadow DOM

Viduni Wickramarachchi
Bits and Pieces

--

As a frontend developer working with a large team, one major hassle I have come across is managing styles for components. Having a shared styles file for components will always result in styling conflicts as it is quite common for two developers to name a style class using the same name.

CSS won’t throw an error for this. As a result, I have experienced unexpected style usage in the components where its time consuming to resolve those.

The technique I am going to introduce next will give you a solution to avoid CSS naming collisions.

What is a Shadow DOM?

Shadow DOM is part of the DOM, which is capable of isolating JavaScript and CSS. If you have heard of iframes, Shadow DOM is also something that has similar capabilities.

Just like in iframes, by default, styles within a shadow DOM won’t leak out and styles outside ofit won’t leak in.

There are ways to inherit some styles from outside of the Shadow DOM too, if required.

Structure of a Shadow DOM

CSS libraries like Styled-components also solve the name collision issue by generating a random class name like .kjkgh2en3. However,

Shadow DOM offers style encapsulation by design!

Next, let’s have a look at how to add a shadow DOM.

Can we use it in Any Project?

Although modern browsers support Web Component Specification, there are some challenges if we try to use it for existing applications.

It’s more like standardizing the Component Definitions native to browsers, where you can create new elements as we do with React, Angular &, etc.

Therefore, it’s essential to check out the feasibility of adopting Shadow DOM, depending on the component libraries you use.

Adding a Shadow DOM

There are two ways to add a shadow DOM.

  1. Open mode
  2. Closed mode

Open mode

class MyComponentOpenRoot extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.close = this.close.bind(this);
}
}

Attaching a shadow DOM with mode: 'open' saves a reference to the shadow root on element.shadowRoot. Therefore, we can access the shadow root via this reference.

Closed mode

Opposed to the open mode, the closed mode does not store a reference. We will have to create our own storage and retrieval way using either a Weak Map or an Object if we use the closed mode.

const shadowRoots = new WeakMap();class MyComponentClosedRoot extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'closed' });
shadowRoots.set(this, shadowRoot);
}
connectedCallback() {
const shadowRoot = shadowRoots.get(this);
shadowRoot.innerHTML = `<h1>Hello!</h1>`;
}
}

Now that we have created a Shadow DOM, let’s look at how to attach and apply styles to the Shadow DOM nodes.

We will be using the open mode here onwards.

Attaching styles in the Shadow DOM

class MyComponentOpenRoot extends HTMLElement {  
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.close = this.close.bind(this);
}

connectedCallback() {
const { shadowRoot } = this;
shadowRoot.innerHTML = `<style>
.wrapper {
opacity: 0;
transition: visibility 0s, opacity 0.25s ease-in;
}

.overlay {
height: 100%;
position: fixed;
top: 0;
right: 0;
}

button {
cursor: pointer;
font-size: 1.25rem;
}
</style>
<div class="wrapper">
<div class="overlay"></div>
<div>
<button class="close">✖️</button>
<h1 id="title">Hello world</h1>
<div id="content" class="content">
<p>This is content outside of the shadow DOM</p>
</div>
</div>
</div>`;
}
}

All the styling required for the Shadow DOM nodes can be specified in the above manner. These styles will only apply to nodes inside the Shadow DOM as they are attached to the shadow root.

The Shadow DOM will be shown as below in the Chrome Dev Tools DOM elements.

So far, Shadow DOM seems very appealing. Finally, we have a way to scope out CSS without any hacks!

Using Shadow DOM in Practice

  1. There are alternatives to Shadow DOM, such as styled-components. However, since styled-components have CSS in JS, it is less performant than Shadow DOM. But if you already have styled-components in your project, you can stick to it rather than moving to Shadow DOM for style encapsulation.
  2. Even though Shadow DOM provides some powerful tools, we should not use it always. For example, if you have an <form> element in your DOM, you shouldn’t use Shadow DOM for components inside the form, such as input or textarea. This is because you cannot query nodes inside the shadow tree from the outside. Therefore, the form will ignore the components where Shadow DOM is used.
  3. If you want to style your component using a global CSS stylesheet, you should avoid using Shadow DOM in that particular component, as the global styles will not be applied if you use it.
  4. You should handle event propagation with care if you are using Shadow DOM in your component, as the use of UI events is different in and out of the shadow boundary.
  5. If you are using many third-party plugins or integrations in your application, you should be aware that some of these may not work with components that have Shadow DOM as they are not built to deal with it.
  6. Shadow DOM supports <slot> elements where you can render components from the light DOM to your Shadow DOM positions. When <slot> is used, the browser performs composition and renders light DOM elements in corresponding slots in the Shadow DOM.
  7. There are more ways of declaring a Shadow DOM as well. For example, Declarative Shadow DOM, which brings the Shadow DOM to the server. This is an exciting area to explore if you are interested in learning more about the Shadow DOM.
  8. Encapsulated styling is essential when sharing components between different web projects using tools like Bit. A reusable component should be context-agnostic - which should include preventing any possible styling/naming conflicts in possible hosting environments. For example, the following styled component I have shared on Bit can be used in any project without worrying about style conflicts.

Learn more about sharing styling rules as reusable components here:

Shadow DOM provides out-of-the-box style isolation and DOM encapsulation, perfect for self-contained, reusable components for your UIs.

Browser Compatibility

If your application needs to be run on Internet Explorer, Shadow DOM is not the way for you as it has no support for Internet Explorer. But the rest of the commonly used browsers comes with support for Shadow DOM.

Source: MDN

Summary

If you are interested in productive and efficient frontend development without worrying about style class name collision, Shadow DOM is a great option. Whether to use Shadow DOM or use a CSS library such as styled-components to avoid name collisions is entirely up to you.

Personally, I prefer Shadow DOM, as styled-components use CSS in JS, which can be less performant. With Shadow DOM, component authors are in full control as to how to represent their component.

If you want more insight into advanced Shadow DOM concepts, let me know in the comments section below. Shadow DOM is a massive topic with a lot of concepts to learn about and explore.

I hope you learned the importance of Shadow DOM through this article. Thanks for reading!

Build better Component Libs and Design Systems

Bit’s open-source tool help 250,000+ devs to build apps with components.

Turn any UI, feature, or page into a reusable component — and share it across your applications. It’s easier to collaborate and build faster.

Learn more

Split apps into components to make app development easier, and enjoy the best experience for the workflows you want:

Micro-Frontends

Design System

Code-Sharing and reuse

Monorepo

Learn more:

--

--