Give us a call: (800) 252-6164

Using the Shadow DOM in 2024

July 25, 2023 | By David Selden-Treiman | Filed in: Javascript.

Welcome to this comprehensive exploration of Shadow DOM! If you’re venturing into the advanced realms of web development, or just dipping your toes into the world of HTML, you’ve probably heard about this intriguing concept. Shadow DOM may sound somewhat mysterious—like a superhero’s secret identity—but don’t worry, by the end of this guide, you’ll be comfortably familiar with what it is, why it’s used, and how you can use it to create better, more efficient web components.

But before we dive deep into the world of Shadow DOM, let’s clear up one crucial detail: Shadow DOM isn’t a separate language or a new technology you have to learn from scratch. Rather, it’s a part of the HTML standard itself, a potent tool in the world of Web Components. It’s much like that hidden pocket in your favorite jacket—the one you didn’t know about when you first bought it, but now can’t live without.

Before we embark on our journey of understanding Shadow DOM, let’s paint a picture of what lies ahead. We’ll start by visiting the broader landscape of the Document Object Model (DOM) where Shadow DOM resides. Then we’ll unravel the concept of Shadow DOM itself, diving into how it functions and its primary components. We’ll also walk through the benefits of using Shadow DOM, illustrating how it can make your web development process more efficient and your code more maintainable. We’ll look at the different parts of the Shadow DOM, like shadow roots and shadow trees, and, finally, guide you through a hands-on section about implementing Shadow DOM in your projects.

But, let’s not get ahead of ourselves. For now, think of Shadow DOM as a helpful ally on your web development journey. It might seem a bit complex at first, but once you understand it, it becomes an invaluable tool in your toolkit—one that enables you to craft clean, encapsulated, and reusable web components with ease.

So grab your favorite cup of coffee (or tea, if that’s your preference), and get comfortable. We’re about to venture into the fascinating world of Shadow DOM. Let’s dive in together!

Understanding the Document Object Model (DOM)

Before we explore the Shadow DOM, let’s first take a detour to understand the broader universe it lives within—the Document Object Model (DOM). Now, if you’ve worked with HTML before, you’ve worked with the DOM, even if you didn’t know it by that name.

Imagine you’ve painstakingly built a beautiful sandcastle. You have towers, turrets, and even a tiny drawbridge. In essence, that’s what your HTML code is—an intricately designed sandcastle. Now, imagine a blueprint of your sandcastle that outlines all the towers, the turrets, and the drawbridge in a structural, hierarchical layout. That blueprint? That’s the DOM for your HTML sandcastle.

To put it simply, the DOM is a representation of your HTML document that is read and manipulated by the browser to present your webpage. It’s structured as a tree of objects—often referred to as the “DOM Tree”—with each HTML element represented as a “node” in this tree.

Let’s look at a small snippet of HTML code:

<!DOCTYPE html>
<html>
  <head>
    <title>My First Web Page</title>
  </head>
  <body>
    <header>
      <h1>Welcome to my page!</h1>
    </header>
    <main>
      <p>This is my first webpage.</p>
    </main>
  </body>
</html>

Here, the <html> element is the root of the tree, while the <head> and <body> elements are its children. Likewise, <header> and <main> are children of <body>, and so on. In this way, the DOM organizes the entire HTML document in a hierarchical, parent-child relationship, just like a family tree!

But why does this matter? Well, understanding the DOM is crucial for two reasons:

  1. JavaScript Interactions: JavaScript interacts with HTML through the DOM. It can be used to add, modify, or remove elements, making your webpage dynamic and interactive.
  2. CSS Styling: CSS styles are also applied through the DOM tree. They can be inherited or cascade down from parent elements to child elements, shaping the visual look of your webpage.

With this context in mind, we can now delve into the concept of “shadow trees” within the DOM and our main topic—Shadow DOM. Ready? Let’s go!

Exploring the Shadow DOM

After our quick detour to understand the DOM, it’s time to turn our spotlight back to the star of the show—the Shadow DOM. You’re already familiar with the concept of a “DOM tree,” so now, think of Shadow DOM as a hidden, or “shadow,” subtree attached to elements of your main DOM tree. It’s like an invisible backpack that an element can carry around, filled with its own private DOM tree.

What is the Shadow DOM?

The Shadow DOM is a web standard that developers use to encapsulate their HTML, CSS, and JavaScript. It allows you to create a separate DOM tree with its own set of elements, styles, and events, hiding them away from the main document’s DOM tree.

Think of it as a bubble of code that lives inside an element. From the outside, the bubble is transparent—you don’t see what’s inside it. But inside that bubble, you can have a whole universe of HTML elements, styles, and scripts that don’t interfere with the rest of your page.

Here’s a simplified example. Imagine you have an HTML document with a <div> element:

<div id="shadowHost"></div>

You could attach a shadow root to this <div> element:

let shadowRoot = document.getElementById('shadowHost').attachShadow({mode: 'open'});

And then you can add some HTML to this shadow root:

shadowRoot.innerHTML = '<p>Welcome to the Shadow DOM!</p>';

Now, if you inspect your <div> element in the browser, you’ll see that it contains a #shadow-root, inside which lives your <p> element. But interestingly, the main document’s DOM tree doesn’t show this <p> element—it’s hidden inside your shadow DOM!

How does the Shadow DOM work?

The key to Shadow DOM’s power is encapsulation. The contents of a shadow DOM are separate from the main document. This means that styles and scripts inside a shadow DOM don’t bleed out, and styles and scripts outside the shadow DOM don’t bleed in.

For example, if you have a CSS rule in your main document like p { color: red; }, it won’t turn the text inside your shadow DOM red. And if you have a rule inside your shadow DOM like p { font-size: 20px; }, it won’t affect any <p> elements in your main document.

This encapsulation also extends to JavaScript. Events inside your shadow DOM don’t bubble out to the main document, and the scripts inside your shadow DOM can’t accidentally affect elements in your main document.

What are the main components of the Shadow DOM?

At its core, the Shadow DOM consists of two main components:

  1. Shadow host: This is the regular DOM node that the Shadow DOM attaches to. In our example above, the <div> element with the id shadowHost is the shadow host.
  2. Shadow tree: This is the separate DOM tree that starts at the shadow root and contains all the elements inside the Shadow DOM.

You can think of the shadow host as a tree with a hidden, “shadowy” subtree—the shadow tree. And with that, we’ve grasped the basics of what the Shadow DOM is and how it works! Now, let’s delve into why it’s such a game-changer for web development.

Benefits of Using the Shadow DOM

Now that you have a handle on what the Shadow DOM is, you might be asking, “Why should I use it?” Excellent question! Let’s look at several key benefits of using the Shadow DOM in your web development process.

Encapsulation and Style Conflict Prevention

Remember when we said that the Shadow DOM is like a bubble? This isn’t just a fancy metaphor. This bubble provides a crucial feature known as encapsulation, which is a fancy way of saying that what happens in the Shadow DOM stays in the Shadow DOM.

This is particularly beneficial when it comes to CSS. Normally, CSS rules can cascade down and affect any matching elements in your HTML. If you’ve ever had a CSS rule unintentionally affect parts of your webpage that it wasn’t supposed to, you’ve experienced a lack of encapsulation.

With Shadow DOM, styles defined within the shadow tree won’t affect the main document, and styles from the main document won’t affect the contents of the shadow tree. This prevents style conflicts and makes your styles modular, which is a significant win when you’re developing large, complex websites or web apps.

Here’s an example. Let’s say you have this CSS rule in your main document:

p { color: red; }

And in your Shadow DOM, you have this HTML:

<div id="shadowHost"></div>

With this JavaScript:

let shadowRoot = document.getElementById('shadowHost').attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<p>This is some text inside the Shadow DOM.</p>';

Despite the CSS rule in the main document, the text inside the Shadow DOM won’t be red, because the styles in the main document can’t reach inside the Shadow DOM. Likewise, any styles you define inside the Shadow DOM won’t affect elements in the main document.

Local and Scoped Scripts

Just like with styles, scripts inside the Shadow DOM are also encapsulated. This means that your JavaScript can be specific and local to the part of your web page where it’s needed, without the risk of it unintentionally affecting other elements in your document.

This scoped behavior is particularly useful when working with events. In the regular DOM, events can bubble up and be captured down the DOM tree, which can sometimes lead to unexpected behavior. But events inside the Shadow DOM are confined to the shadow tree and won’t bubble out to the main document, keeping them localized and predictable.

Modularity and Reusability

Another significant advantage of the Shadow DOM is the modularity it brings to web development. Because the Shadow DOM encapsulates HTML, CSS, and JavaScript together, you can create self-contained, reusable components that are easy to maintain and can be used across different projects.

Consider building a custom video player. Without Shadow DOM, your HTML markup, styles, and scripts could end up scattered across your document, making it hard to understand, maintain, or reuse. But with Shadow DOM, you can encapsulate everything inside a custom HTML element—like <my-video-player>—and reuse it wherever you need a video player, just like you’d use a regular HTML element!

Enhancing Performance and Load Times

Finally, using Shadow DOM can enhance the performance of your website or web application. By scoping styles and scripts to specific parts of your page where they’re needed, you can keep your codebase clean and efficient, which can lead to faster load times and smoother performance.

All these advantages make Shadow DOM a powerful tool in your web development arsenal. It can help you write cleaner, more maintainable, and more efficient code, ultimately making your life as a developer a little bit easier! Now, let’s dive deeper and understand the workings of shadow roots and shadow trees in the next section.

Diving Deeper: Shadow Roots and Shadow Trees

By now, you’ve learned the basics of Shadow DOM, grasped why it’s beneficial, and even started to dabble with creating your own shadow trees. However, to fully understand and effectively use Shadow DOM, we need to delve deeper into two essential components: the shadow root and the shadow tree.

The Shadow Root

The shadow root is a special kind of document fragment that becomes the root node of a shadow tree. It’s like the base of your tree, where all other nodes and elements grow from.

When you create a shadow root using the attachShadow() method, you pass in a configuration object with a mode property. This mode can be either 'open' or 'closed'. An open shadow root is accessible and can be manipulated from outside scripts, while a closed shadow root is not.

let shadowHost = document.querySelector('#shadowHost');
let shadowRoot = shadowHost.attachShadow({mode: 'open'});

In the above code, we’re creating an open shadow root. We can then access this shadow root from outside scripts using the shadowRoot property:

let shadowRoot = shadowHost.shadowRoot;
console.log(shadowRoot);

On the other hand, if you create a closed shadow root, you can’t access it from outside scripts:

let shadowHost = document.querySelector('#shadowHost');
let shadowRoot = shadowHost.attachShadow({mode: 'closed'});

Now, if you try to access the shadow root, you’ll get null:

let shadowRoot = shadowHost.shadowRoot;
console.log(shadowRoot); // Outputs: null

It’s important to note that the openness or closedness of a shadow root doesn’t affect its encapsulation features—styles and scripts are still confined to the shadow tree whether the root is open or closed. The mode property primarily controls whether the shadow root can be accessed from outside scripts.

The Shadow Tree

The shadow tree is the DOM tree that starts at a shadow root and includes all its children. It’s essentially a mini-DOM inside your shadow root.

Once you have your shadow root, you can add elements to your shadow tree just like you would with a regular DOM tree. You can use the innerHTML property:

shadowRoot.innerHTML = '<p>Welcome to the shadow tree!</p>';

Or you can create elements and append them to the shadow tree:

let p = document.createElement('p');
p.textContent = 'Welcome to the shadow tree!';
shadowRoot.appendChild(p);

In both cases, the <p> element becomes a part of the shadow tree and is encapsulated within the shadow root.

And there you have it! With the knowledge of shadow roots and shadow trees, you’ve now taken another step towards mastering Shadow DOM. Let’s move to the final section where we will guide you through implementing the Shadow DOM in your projects.

Implementing the Shadow DOM in Your Projects

By now, you’ve learned a lot about the Shadow DOM, and you might be excited to try it out in your own projects. But where do you start? In this section, we’ll walk you through the steps of implementing the Shadow DOM.

Creating a Shadow Root

First things first, you need to create a shadow root. Select an existing DOM node to be your shadow host. Then, use the attachShadow() method to attach a shadow root to it:

let shadowHost = document.querySelector('#shadowHost');
let shadowRoot = shadowHost.attachShadow({mode: 'open'});

With this, you’ve created a shadow root, and your shadow host is ready to have a shadow tree!

Adding Elements to the Shadow Tree

Next, you’ll want to populate your shadow tree with some elements. You can do this in several ways. One approach is to use the innerHTML property:

shadowRoot.innerHTML = `
  <style>
    p {
      color: blue;
    }
  </style>
  <p>Welcome to the Shadow DOM!</p>
`;

Another way is to create elements and append them to your shadow root:

let style = document.createElement('style');
style.textContent = `
  p {
    color: blue;
  }
`;

let p = document.createElement('p');
p.textContent = 'Welcome to the Shadow DOM!';

shadowRoot.appendChild(style);
shadowRoot.appendChild(p);

Both of these methods create the same shadow tree—a <style> element and a <p> element, both encapsulated inside the shadow root.

Manipulating the Shadow Tree

Just like a regular DOM tree, you can manipulate a shadow tree using JavaScript. For example, you can add, modify, or remove elements:

// Add a new element
let div = document.createElement('div');
div.textContent = 'This is a new div element!';
shadowRoot.appendChild(div);

// Modify an existing element
let p = shadowRoot.querySelector('p');
p.textContent = 'This is the updated text!';

// Remove an element
shadowRoot.removeChild(div);

With these operations, you can make your shadow tree dynamic and interactive.

Styling the Shadow Tree

Remember that styles inside the shadow tree are scoped to the shadow tree—they won’t affect the main document, and styles from the main document won’t affect them. To style elements in your shadow tree, you can include a <style> element inside the shadow root:

shadowRoot.innerHTML = `
  <style>
    p {
      color: blue;
      font-size: 20px;
    }
  </style>
  <p>Welcome to the Shadow DOM!</p>
`;

In this example, the <p> element inside the shadow tree will be blue and have a font size of 20px, but <p> elements outside the shadow tree won’t be affected by these styles.

Implementing the Shadow DOM with Custom Elements

To take your Shadow DOM usage to the next level, you can combine it with custom elements to create reusable web components. Custom elements allow you to define your own HTML elements, and when combined with Shadow DOM, you can encapsulate the structure, style, and behavior of these elements, making them reusable across different projects.

We won’t delve into the details of custom elements in this article, but here’s a basic example of how you can use them with Shadow DOM:

class MyElement extends HTMLElement {
  constructor() {
    super();
    let shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = `
      <style>
        p {
          color: blue;
        }
      </style>
      <p>Hello from a custom element!</p>
    `;
  }
}

customElements.define('my-element', MyElement);

Now, you can use <my-element> just like any other HTML element, and it will have its own encapsulated Shadow DOM!

<my-element></my-element>

And there you have it! You’re now ready to start using the Shadow DOM in your own projects. As you get more comfortable with it, you’ll discover it’s a powerful tool for creating modular, maintainable, and efficient web applications. Happy coding!

Overcoming Potential Challenges with the Shadow DOM

It’s important to remember that while the Shadow DOM provides a host of benefits, there can also be a few challenges you might face. But don’t worry, as each of these challenges can be overcome with the right approach and a better understanding.

Browser Compatibility

While Shadow DOM is supported by most modern browsers including Chrome, Firefox, Safari, and Edge, it may not be compatible with some older or less common browsers. This is where tools like polyfills come in handy. Polyfills are pieces of code that implement features on web browsers that do not support those features. You can use polyfills like the ones provided by the WebComponents project to ensure compatibility across a wider range of browsers.

<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/2.1.3/webcomponents-bundle.js"></script>

Remember to always check the current browser compatibility at sites like Can I use before deciding to implement Shadow DOM.

Debugging

Shadow DOM’s encapsulation can sometimes make debugging a little tricky since elements inside a shadow root are not visible in the Elements tab of your browser’s developer tools by default. But here’s a simple solution: In Chrome DevTools, you can enable ‘Show user agent shadow DOM’ in the settings to display shadow roots and their content in the Elements tab.

CSS Custom Properties

While encapsulation of styles is generally a benefit, there are times when you might want to style some aspects of a shadow tree from the outside or need to expose some styles to be customized. In this case, CSS Custom Properties (or CSS Variables) can come to your rescue.

CSS variables can pierce through the Shadow DOM boundaries and are applied to all instances of a component. Here’s how you can use them:

In your shadow tree:

<style>
  p {
    color: var(--text-color, black);
  }
</style>
<p>Stylish text!</p>

In your main document:

<my-element style="--text-color: blue;"></my-element>

In this example, the paragraph inside my-element will be blue because the --text-color variable is set to blue in the main document. The black value in the var() function in the shadow tree’s style is a fallback—it will be used if --text-color is not defined in the main document.

Events

While events within the Shadow DOM do not bubble outside it, sometimes, you might need to listen for events originating in the shadow tree from the main document. The solution here is to use the composed flag when creating and dispatching an event. Events marked as composed can pass through shadow boundaries.

let event = new Event('my-event', {bubbles: true, composed: true});
shadowRoot.dispatchEvent(event);

In this example, the ‘my-event’ event can be listened for from the main document, even though it’s dispatched from within the shadow root, thanks to the composed: true flag.

By understanding these challenges and their solutions, you can make the most out of Shadow DOM while avoiding potential pitfalls. Remember, practice makes perfect. The more you work with the Shadow DOM, the more comfortable you’ll become. Keep exploring, keep experimenting, and most importantly, keep creating!

Using XPath with the Shadow DOM and JavaScript

XPath is a powerful language that is used to navigate through elements and attributes in XML documents. However, because of the way Shadow DOM encapsulates its contents, XPath expressions that work for the light DOM won’t directly work for the shadow DOM. This is because XPath operates on the concept of a single, unified document tree, which conflicts with the encapsulation provided by the Shadow DOM.

However, we can use JavaScript to create XPath-like selectors that can traverse both light DOM and shadow DOM. Here is a basic guideline:

Accessing Shadow DOM Elements

First, we need to access the shadow root of the element. Let’s say we have a custom web component <my-component> which has a shadow DOM. Here’s how you would access its shadow root:

let shadowRoot = document.querySelector('my-component').shadowRoot;

Traversing Shadow DOM Elements

From there, you can navigate to the elements within the shadow root using querySelector() or querySelectorAll(), which provide similar functionality to XPath for the purposes of DOM traversal.

let shadowElement = shadowRoot.querySelector('.my-class');

In this case, the JavaScript querySelector() method provides a way to traverse the shadow DOM similarly to how you would with XPath, finding the first element with the class my-class.

If you want to select multiple elements, you can use querySelectorAll():

let shadowElements = shadowRoot.querySelectorAll('.my-class');

This will return a NodeList of all elements with the class my-class.

XPath within Shadow DOM

If you still need to use XPath specifically within the Shadow DOM, one approach is to convert the Shadow DOM subtree to a string using XMLSerializer, then parse it into an XML document that you can use XPath on:

let serializer = new XMLSerializer();
let shadowRoot = document.querySelector('my-component').shadowRoot;
let str = serializer.serializeToString(shadowRoot);
let parser = new DOMParser();
let xmlDoc = parser.parseFromString(str, "application/xml");

let xpathResult = xmlDoc.evaluate("/p[1]", xmlDoc, null, XPathResult.ANY_TYPE, null); 
let firstParagraph = xpathResult.iterateNext();

console.log(firstParagraph.textContent);

In this example, we serialize the shadow DOM to a string, parse it into an XML document, and then use XPath to select the first <p> element.

However, keep in mind that this method involves some overhead and might not work for all situations, since XML has stricter syntax rules than HTML.

While XPath isn’t directly compatible with the Shadow DOM, there are workarounds available. JavaScript provides plenty of powerful methods for DOM traversal and manipulation, and with these tools in your toolkit, you can navigate and manipulate the Shadow DOM effectively.

Why Ad Providers Like Outbrain Are Using the Shadow DOM

As you now know, the Shadow DOM offers several advantages that make it ideal for creating encapsulated, reusable components on web pages. These benefits are increasingly being leveraged by ad providers like Outbrain to improve the way they display ads. Let’s delve into some of the key reasons why ad providers are migrating towards using the Shadow DOM.

Isolation

The encapsulation provided by the Shadow DOM means that an ad’s content, styles, and scripts can be isolated from the rest of the web page. This prevents ads from inadvertently affecting or being affected by the styles or scripts of the main web page. This isolation can lead to fewer rendering issues and a smoother overall user experience.

For instance, without the Shadow DOM, an ad provider might run into trouble if the main page has a CSS rule that styles all <img> elements, as it could unintentionally impact the ad’s images. With Shadow DOM, ad providers can ensure their ad looks and behaves as expected, regardless of the surrounding page’s CSS or JavaScript.

Reusability

Ad providers often need to display the same ad in different contexts or on different websites. The Shadow DOM, when combined with custom elements, allows them to define an ad as a reusable web component. This means they can create an ad once, encapsulate it within a custom element, and then reuse it wherever needed. This can result in cleaner, more maintainable code and can save time and resources in the ad production process.

Security

The isolation of the Shadow DOM also provides a measure of security. Since the ad lives in a separate scope from the main page, it’s more difficult for malicious code on the main page to interact with the ad. This makes it harder for bad actors to manipulate ads or use them as vectors for attacks.

Unobtrusiveness

The Shadow DOM can make ads less obtrusive. Since shadow trees are not visible in the page’s HTML source by default, ads served through the Shadow DOM can reduce clutter in the page source and make the HTML easier to read and understand for developers.

While these are some of the main reasons ad providers are moving towards the Shadow DOM, it’s important to remember that like any technology, the Shadow DOM should be used judiciously and responsibly. Ad providers should strive to create ads that respect users’ privacy and provide a positive, non-intrusive experience, and web developers should familiarize themselves with the Shadow DOM to better understand how ads and other components might interact with their pages.

Conclusion: Harnessing the Power of Shadow DOM

Congratulations, you’ve made it to the end of this deep dive into the Shadow DOM! You’ve learned what the Shadow DOM is, why it’s used, and how to use it effectively in your projects. You’ve also discovered the concept of encapsulation, the power of shadow roots and shadow trees, and ways to overcome some challenges you may encounter.

The Shadow DOM is not just an abstract concept, but a practical tool you can use to improve your web applications. You can use it to create reusable components, protect your styles from bleeding into other parts of your application, and keep your code organized and maintainable.

But your journey doesn’t end here. There’s always more to learn and discover. For instance, you can explore how the Shadow DOM works in conjunction with other web components APIs like custom elements and HTML templates. You can also delve into slotting, a technique for defining placeholder content in your shadow trees that can be filled by your shadow host.

Just remember: practice makes perfect. The more you work with the Shadow DOM, the more comfortable you’ll become, and the more you’ll start to see its true power. Start small—try creating a simple shadow tree and adding some elements. Then, as you gain confidence, start incorporating the Shadow DOM into larger projects.

The world of web development is always evolving, and new technologies and techniques are continually emerging. By learning and mastering tools like the Shadow DOM, you’re staying at the forefront of this exciting field. Keep coding, keep learning, and keep pushing the boundaries of what’s possible on the web. Happy coding!

David Selden-Treiman, Director of Operations at Potent Pages.

David Selden-Treiman is Director of Operations and a project manager at Potent Pages. He specializes in custom web crawler development, website optimization, server management, web application development, and custom programming. Working at Potent Pages since 2012 and programming since 2003, David has extensive expertise solving problems using programming for dozens of clients. He also has extensive experience managing and optimizing servers, managing dozens of servers for both Potent Pages and other clients.


Tags:

Comments are closed here.

Javascript Techniques

Shadow DOM

The shadow DOM allows you to create content separate from your main page. This is useful for isolating your styles and scripts from the main page.

Web Workers

Web workers can be very useful for speeding up a site, moving Javascript tasks off of the main thread and onto separate threads for processing. There are also ways to improve web worker performance.

Service Workers

Service workers can allow you to do tasks independently of your website, even if the site is disconnected from the Internet or the page is closed.

Web Storage API

The web storage API allows you to store data in users' browsers with easier access.

IndexedDB API

The IndexedDB API is excellent for storing data in browsers for modern web applications.

Javascript Worklets API

The Javascript Workets API allows you to offload animations and other Javascript tasks off of your main thread, potentially speeding up the performance of your site.

Async vs Defer

Knowing the difference between Async and Defer can help to improve the performance of your site by not forcing the main thread to wait for your scripts to load before continuing.

Scroll To Top