DEV Community

Cover image for Understanding the Shadow DOM and When to Use It
Med Marrouchi
Med Marrouchi

Posted on

Understanding the Shadow DOM and When to Use It

Understanding the Shadow DOM and When to Use It

The Shadow DOM is a powerful feature in the modern web development toolkit that helps developers encapsulate elements and isolate styles. Essentially, the Shadow DOM allows you to create a "mini-DOM" inside an element that is completely isolated from the rest of the page. This means that the CSS and JavaScript inside this shadow DOM won't interfere with anything outside of it, and vice versa.

One of the key problems the Shadow DOM solves is CSS style leakage—where styles defined for one part of your application inadvertently affect other parts, leading to a lack of predictability and harder-to-maintain code. The Shadow DOM creates a style boundary, preventing this issue.

Here's an example use case where the Shadow DOM is particularly useful:

Imagine you have a chatbot widget—like the Hexabot widget—that you want to embed on multiple websites. Each of these websites has its own CSS, and some styles might interfere with how your widget should look and behave. For instance, a website might have global styles for <div> elements, and if your widget's elements are simply added to the DOM, those styles could change the look and feel of your widget.

To prevent the website's CSS from conflicting with your widget's CSS, you can leverage the Shadow DOM to encapsulate your widget. Here's a simple example to illustrate this:

Without Shadow DOM:

<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<link rel="stylesheet" href="./style.css">
<script src="<<WIDGET URL>>/hexabot-widget.umd.js"></script>

<div id="hb-chat-widget"></div>
<script>
  const el = React.createElement;
  const domContainer = document.getElementById('hb-chat-widget');
  ReactDOM.render(
    el(HexabotWidget, {
      apiUrl: 'https://api.yourdomain.com',
      channel: 'offline',
      token: 'token123',
    }),
    domContainer,
  );
</script>
Enter fullscreen mode Exit fullscreen mode

In this example, any global styles from the website might interfere with the widget's look.

With Shadow DOM:

<script crossorigin src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="<<WIDGET URL>>/hexabot-widget.umd.js"></script>

<div id="hb-chat-widget"></div>
<script>
  // Create the shadow root and attach it to the widget container
  const widgetContainer = document.getElementById('hb-chat-widget');
  const shadowRoot = widgetContainer.attachShadow({ mode: 'open' });

  // Create a new div inside the shadow root to serve as the rendering target
  const shadowContainer = document.createElement('div');
  shadowRoot.appendChild(shadowContainer);

  // Add styles inside the shadow root by importing the CSS file into the shadow DOM
  const linkElement = document.createElement('link');
  linkElement.rel = 'stylesheet';
  linkElement.href = './style.css';
  shadowRoot.appendChild(linkElement);

  // Render the widget inside the shadow root
  const el = React.createElement;
  ReactDOM.render(
    el(HexabotWidget, {
      apiUrl: 'https://api.yourdomain.com',
      channel: 'offline',
      token: 'token123',
    }),
    shadowContainer,
  );
</script>
Enter fullscreen mode Exit fullscreen mode

In this version, the widget is rendered inside a shadow root. This means that the styles defined for the website won't impact the widget, and vice versa. The CSS styles for your widget are kept isolated, ensuring a consistent look across any website the widget is embedded in.

When Should You Use the Shadow DOM?

The Shadow DOM is useful whenever you need to create self-contained components that won't be affected by or affect other parts of the application. Here are some scenarios where you should consider using it:

  • Widgets or Plugins: When developing reusable widgets that could be embedded in various environments, using the Shadow DOM will prevent external CSS conflicts.
  • Complex UI Components: If you're building custom elements like sliders, carousels, or other UI components where you want tight control over styling.
  • Isolation Needs: Any scenario where you need complete isolation of CSS and JavaScript to avoid unintentional interactions.

By encapsulating the styles and behavior of a component, the Shadow DOM can be a crucial tool for developers building modular, reusable, and robust web components.

The Hexabot live chat widget uses this method to ensure a seamless and consistent user experience across different websites, without any interference from external styles. If you're interested in seeing this in action, feel free to check out Hexabot and star the GitHub repository to support the project!


 Star the Hexabot Github Repository ⭐

Top comments (2)

Collapse
 
dannyengelman profile image
Danny Engelman • Edited
  • attachShadow({mode:"open") sets and returns a .shadowRoot property for free
  • appendCild is oldskool; modern browsers have .append for adding multiple Nodes
  • With one createElement helper function

That makes it:

const createElement = (tag, props = {}) => Object.assign(document.createElement(tag), props);
document
    .getElementById('hb-chat-widget')
    .attachShadow({ mode: 'open' })
    .append( 
       createElement("div"),
       createElement("link", {
         rel: "stylesheet",
         href: "./style.css"
       });
     )
Enter fullscreen mode Exit fullscreen mode
Collapse
 
marrouchi profile image
Med Marrouchi

Thanks @dannyengelman I will update accordingly