DEV Community

Cover image for Shadow DOM: What it is and how it works
Stefan Nieuwenhuis
Stefan Nieuwenhuis

Posted on • Originally published at nhswd.com

Shadow DOM: What it is and how it works

The web has come a long way since the early days. Today, developers can create complex web applications using a variety of tools and frameworks. However, with this complexity comes a new set of challenges, such as ensuring that the styles and behavior of a web application are consistent across different browsers and platforms. This is where the Shadow DOM comes in.

In this article, we'll explore the Shadow DOM in more detail, including its syntax, use cases, advantages, and disadvantages.

What is Shadow DOM?

Shadow DOM is a technology that enables developers to create encapsulated DOM trees, which are separate from the main document tree. These DOM trees are attached to custom elements, and provide a way to isolate the styles and behavior of the custom element from the rest of the document. This means that styles and behaviors defined in the Shadow DOM will only affect the custom element, and not any other elements on the page.

How does it work?

When a custom element is defined with Shadow DOM, it creates a new Document Fragment that is attached to the custom element. This Document Fragment contains a new DOM tree that is separate from the main document tree. Any styles or behaviors defined in the Shadow DOM will only affect elements within this new DOM tree.

To attach Shadow DOM to a custom element, developers use the attachShadow() method of the HTMLElement class. This method takes an object with two properties: mode and delegatesFocus. The mode property determines whether the Shadow DOM tree is open or closed, while the delegatesFocus property determines whether the custom element can receive keyboard focus.

class MyElement extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    // Add elements to shadow DOM tree here
  }
}
Enter fullscreen mode Exit fullscreen mode

Once we've created the Shadow DOM tree, we can add elements to it just like we would with the regular DOM. Here's an example:

class MyElement extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    const div = document.createElement('div');
    div.textContent = 'Hello, Shadow DOM!';
    shadow.appendChild(div);
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example, we've created a custom element called MyElement, and attached a Shadow DOM tree to it with the mode set to 'open'. We've then created a new <div/> element, set its text content to 'Hello, Shadow DOM!', and added it to the Shadow DOM tree using the appendChild() method.

Examples of how it can be used

Custom input element

Let's say you want to create a custom input element that looks and behaves differently from the standard HTML input element. You can define a custom element with Shadow DOM, and use CSS to style the input element within the Shadow DOM. Here's an example:

class MyInputElement extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({mode: 'open'});
    const input = document.createElement('input');
    input.setAttribute('type', 'text');
    input.setAttribute('placeholder', 'Enter text here');
    shadow.appendChild(input);
    shadow.style.display = 'inline-block';
    shadow.style.border = '1px solid #ccc';
    shadow.style.padding = '5px';
  }
}

customElements.define('my-input', MyInputElement);
Enter fullscreen mode Exit fullscreen mode

In this example, we've created a new custom input element called MyInputElement. We've attached Shadow DOM to this element using the attachShadow() method, and set the mode to 'open'. We then created a new input element within the Shadow DOM, and added some styling using CSS.

To use this custom input element, we simply use the <my-input> tag in our HTML:

<my-input></my-input>
Enter fullscreen mode Exit fullscreen mode

Custom button element

Another example of using Shadow DOM is to create a custom button element. Let's say you want to create a button that has a different look and feel from the standard HTML button element. You can define a custom element with Shadow DOM, and use CSS to style the button within the Shadow DOM. Here's an example:

class MyButtonElement extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({mode: 'open'});
    const button = document.createElement('button');
    button.textContent = this.getAttribute('label') || 'Button';
    shadow.appendChild(button);
    shadow.style.display = 'inline-block';
    shadow.style.border = '1px solid #ccc';
    shadow.style.padding = '5px';
    shadow.style.backgroundColor = '#f0f0f0';
    button.style.border = 'none';
    button.style.backgroundColor = '#f0f0f0
  }
}

customElements.define('my-button', MyButtonElement);
Enter fullscreen mode Exit fullscreen mode

To use this custom input element, we simply use the <my-button> tag in our HTML:

<my-button></my-button>
Enter fullscreen mode Exit fullscreen mode

Use cases

The Shadow DOM has several use cases, including:

  1. Creating reusable web components: With the Shadow DOM, developers can create encapsulated web components that can be used in any web application without interfering with the styles and behavior of other components.

  2. Styling web components: The Shadow DOM provides a way to encapsulate the styles of a web component, so that they don't interfere with the styles of other components on the page.

  3. Creating custom elements with default styles: The Shadow DOM allows developers to define default styles for a custom element, which are applied only to the elements within the Shadow DOM tree.

Advantages of Shadow DOM

Encapsulation

One of the main advantages of Shadow DOM is that it allows developers to encapsulate the styles and behavior of a custom element, preventing it from being affected by styles and scripts outside of its DOM tree. This makes it easier to maintain and reuse custom elements across different parts of the web application.

Improved Styling

With Shadow DOM, developers can use CSS to style their custom elements, without affecting the rest of the document. This means that they can use class names and selectors that would otherwise conflict with styles in the rest of the document.

Improved Performance

Shadow DOM can also improve the performance of web applications by reducing the amount of CSS and JavaScript that needs to be loaded and executed. This is because styles and scripts can be defined within the Shadow DOM tree, rather than in the main document tree.

Disadvantages of Shadow DOM

Complexity

Shadow DOM can be a complex technology to work with, and may require a deep understanding of the web platform and JavaScript to use effectively. This can make it difficult for less experienced developers to use in their web applications.

Legacy Browser Support

Shadow DOM Brower Support

Another potential disadvantage of Shadow DOM is that it is not supported in legacy browsers. This can limit the reach of web applications that rely on Shadow DOM, and may require developers to use fallbacks or alternative solutions for unsupported browsers.

Accessibility

Finally, Shadow DOM can have implications for accessibility, as it may prevent users from being able to navigate or interact with the custom element using keyboard navigation. This can be mitigated by ensuring that the custom element is accessible using alternative methods, such as ARIA attributes.

Conclusion

Shadow DOM is a powerful web technology that can provide many advantages to web developers, including improved encapsulation, styling, and performance. However, it also has some potential disadvantages, such as complexity, limited browser support, and accessibility issues.

Despite these challenges, Shadow DOM remains an important tool in the web developer's toolbox, and can be used effectively in many web applications. To use Shadow DOM effectively, it is important to have a deep understanding of the technology and its implications for web development, and to carefully consider the needs of the application and its users.

Top comments (2)

Collapse
 
dannyengelman profile image
Danny Engelman • Edited

For a Production ready Web Component, I would do:

customElements.define('my-button', class extends HTMLElement {
  constructor() {
    // you CAN use script *before* the super() call! MDN is wrong!
    let element = (tag , props ) => Object.assign(document.createElement(tag), props);
    let CSS = (props) => element("style", { innerText: props });
    super() // sets AND returns 'this' scope
      .attachShadow({mode: 'open'}) // sets AND returns this.shadowRoot
      .append(
        CSS(`:host{display:inline-block;border:1px solid #ccc;padding:5px;background:#f0f0f0}` +
            `button{border:none;background-color:#f0f0f0}`),
        element("button", {
          innerText: this.getAttribute('label') || 'Button',
          onclick: (evt) => alert("FOO!")
        })
      )
  }
});
Enter fullscreen mode Exit fullscreen mode
Collapse
 
zohaib546 profile image
Zohaib Ashraf

great example
thanks