Understanding and Working with the Shadow DOM

# What is the Shadow DOM?

As web applications grow in complexity, managing styles and preventing conflicts between different components becomes increasingly challenging. Enter the Shadow DOM, a key technology that empowers developers to create encapsulated and reusable web components. By providing a way to isolate HTML, CSS, and JavaScript, the Shadow DOM enables the construction of modular and self-contained elements that enhance code organization and maintainability. This article will guide you through the core concepts of the Shadow DOM, demonstrating its practical use and highlighting its role in modern web development.

# Why use Shadow DOM?

Shadow DOM can be a very powerful feature of web development that allows for elements to be created separately from the main document of the website. You can think of these as "sub documents". There are a few benefits to using this:

Encapsulation: as the code within the Shadow DOM are self-contained, this means that the code inside of them is self-contained. This can be useful to avoid the resources from the main document interacting with the code within the Shadow DOM, which could have unexpected side-effects.

Style scoping: as a result of encapsulation, you can include styling within the Shadow DOM that will only have an effect on the elements included within. This can give you the ability to customise certain elements, without impacting others.

# Working with the Shadow DOM

Working with the Shadow DOM requires the use of JavaScript and they can be interacted with programmatically.

# Creating a Shadow DOM

To get started, you'll first need to create a Shadow DOM on your page, consider the following HTML - this will demonstrate where we are wanting to place the code. We will be attaching the Shadow DOM to this element using JavaScript.

<div id="host"></div>

Now, we can come in with JavaScript to create our Shadow DOM, we are going to use the <div> with the ID of "host" as our placeholder:

const host = document.querySelector('#host')
const shadow = host.attachShadow({ mode: 'open' })
const span = document.createElement('span')
span.textContent = "I'm in the shadow DOM"
shadow.appendChild(span)

From a user perspective, they will not notice any different in the <span> that has been created on the page, but under the hood this implementation will allow you to fully take advantage of the benefits that Shadow DOM provides.

# Encapsulation

Having the ability to ensure that CSS and JavaScript on the page does not impact your custom element can be a powerful tool to ensuring that the elements inside your Shadow DOM will operate as expected. Let's look at the example below (source (opens new window)):

const host = document.querySelector('#host')
const shadow = host.attachShadow({ mode: 'open' })
const span = document.createElement('span')
span.textContent = "I'm in the shadow DOM"
shadow.appendChild(span)

const upper = document.querySelector('button#upper')
upper.addEventListener('click', () => {
  const spans = Array.from(document.querySelectorAll('span'))
  for (const span of spans) {
    span.textContent = span.textContent.toUpperCase()
  }
})

const reload = document.querySelector('#reload')
reload.addEventListener('click', () => document.location.reload())

You'll note that there is a <span> created inside a newly created Shadow DOM, this will appear on the page as normal alongside any other <span> elements on the page. An event listener has been added to a button that will cause any <span> in the document to have it's text content changed to uppercase - however, as the Shadow DOM is encapsulated, this will not effect the content inside of the Shadow DOM.

This concept also applies to CSS styles that have been applied to the main document - they will not impact your content that has been placed inside of a Shadow DOM. In order to modify the CSS inside of the Shadow DOM, you would need to add a bit more code to it, such as:

  • Creating a CSSStyleSheet object programmatically.
  • Including a <style> tag inside of your <template> tag.

You can read more about both of these methods here: developer.mozilla.org (opens new window).

# Custom Elements

When combined with custom elements, Shadow DOMs create a useful framework for developing reusable elements that can level up your website, as well as make them more maintainable. Custom elements share a lot of the same characteristics of the Shadow DOM, let's look at them.

Encapsulation: as custom elements have the ability to encapsulate the HTML, CSS, and JavaScript that has been used to create them. This allows for the creation of self-contained elements that are generally more maintainable. Furthermore, this means that it is a lot more difficult for the component to be accidentally modified by other elements that are being manipulated in the main document.

Reusability: as they are self-contained, custom elements can be rapidly reused within a webpage. This means that if you have a webpage with multiple versions of a similar component, you may be able to create a single custom element that can be used to handle the different states with differing information relevant to the document.

Style scoping: styles can be applied to the specific custom element, rather than the document as a whole. This means that if you wish for the <span> element to have a specific style, you can add this into your custom element and it would not effect the rest of the <span> elements in the main document.

Custom elements can be created to extend the features of HTML elements, customising them to the web developers requirements. Behaviours can be defined by the web developer. They can either extend a specific HTML element, such as HTMLImageElement, or just generally extend HTMLElement to create an autonomous custom element. They are developed using JavaScript. For example,

class CustomParagraph extends HTMLParagraphElement {
  constructor() {
    super()
  }
  // Element functionality written in here
}

Custom elements and Shadow DOM go hand-in-hand - without it, custom elements would be fragile and could break with other changes to the main document of the website. For example, selectors within the custom element would be prone to changes using CSS or JS within the main document, breaking it's functionality and rendering it inoperable.

# Shadow DOM and scraping

The encapsulated nature of Shadow DOM makes it difficult for web scraping. Standard selectors, designed for the main document, cannot penetrate the shadow tree, requiring specialized methods to access its content. Let's look at some code that can be used to access the shadow tree.

const shadowHost = document.querySelector('<SELECTOR>')

if (shadowHost) {
  const shadowRoot = shadowHost.shadowRoot

  // Locate the element inside the shadow root that you wish to interact with.
  const innerElm = shadowRoot.querySelector('<SELECTOR>')

  if (innerElm) {
    // Example: innerElm.click();
  }
}

While we acknowledge that the method above is more technical than some users may wish, this is currently the best option for interacting with Shadow DOM in your Axiom.ai automations.

# Shadow DOM and page interactions

For the same reasons mentioned in the scraping section above, programmatically interacting with a page that makes use of Shadow DOM can be difficult for most automation tools. For example, if we ask a script to click a button that has the ID "button", it will fail to find this in the main document of the page where it would be looking - due to the additional requirements, it will often not check the shadow tree for this element as this would slow down any script. There are a few methods of getting around this, such as:

  • Using the keyboard to navigate on the page - see the Press Key(s) (opens new window) step in Axiom.ai.
  • Using code, see the example above.
  • Clicking on the page using coordinates - this isn't really recommended as elements may move depending on the size of the window that the website is open on.

# Wrapping up

The Shadow DOM is a vital tool for building modern, robust web components. It provides crucial encapsulation, isolating styles and DOM structure to prevent conflicts and enhance maintainability. We explored how to create and manipulate shadow trees with JavaScript, showcasing its benefits for style scoping and code isolation.

We also discussed the powerful combination of Shadow DOM and custom elements, highlighting their role in creating reusable and modular components. Finally, we addressed the challenges of web scraping within Shadow DOM, offering a practical approach to accessing encapsulated elements.

By mastering the Shadow DOM, you gain the ability to create cleaner, more maintainable, and scalable web applications. It's an essential skill for any developer aiming to build high-quality web experiences.

Karl Jones

Karl Jones

Karl is a Technical Writer with Axiom.ai with a Computer Science background and 10+ years of customer support experience. In his spare time he enjoys continuing his technical education, reading, gaming, and working on development side projects.

Contents

    Install the Chrome Extension

    Two hours of free runtime, no credit card required