DEV Community

Nitipit Nontasuwan
Nitipit Nontasuwan

Posted on

Shadow DOM "the right way" in 2024 - Isolation, not complication 🤷

For many years, I've read about Shadow DOM in Web Component from many places then just wonder why the implementation is so complicated.

Image description

While I'm working on Adaptive Style Component Framework: Adapter. I found many complicated things when I tried to implement style with Shadow DOM encapsulation. That make me rethink again, is there any easier way to use Shadom DOM ?


To the solution

Image description

If you want to get into the solution right away, this is the idea codes (Not the ideal one but you can get the point). You can read the reason furthur below.

function isolate(element: HTMLElement, isolationElement: HTMLElement) {
  if (!isolationElement) {
    isolationElement = document.createElement('div');
    isolationElement.attachShadow({mode: 'open'});
  }
  element.parentElement.insertBefore(isolationElement, element);
  element.shadowRoot.append(element);
}
Enter fullscreen mode Exit fullscreen mode

Code above will create div with shadowRoot and append the whole element inside the shadowRoot, make it isolated just in place. Then you can code your element as you do in Normal DOM

Next example shows an idea how to implement it in Web Component class

class Component extends HTMLElement {
  isolated(element: HTMLElement) {
    if (!element) {
      element = document.createElement('div');
      element.attachShadow({ mode: 'open' });
    }
      this.parentElement.insertBefore(element, this);
      element.shadowRoot.appendChild(this);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Why ?

Shadow DOM brings "Component Isolation" concept to Web Application. However, the way developers implements it so far is different from what I'm thinking recently.

Let's take a look at the code from https://web.dev/articles/shadowdom-v1

// Use custom elements API v1 to register a new HTML tag and define its JS behavior
// using an ES6 class. Every instance of <fancy-tab> will have this same prototype.
customElements.define('fancy-tabs', class extends HTMLElement {
    constructor() {
    super(); // always call super() first in the constructor.

    // Attach a shadow root to <fancy-tabs>.
    const shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = `
        <style>#tabs { ... }</style> <!-- styles are scoped to fancy-tabs! -->
        <div id="tabs">...</div>
        <div id="panels">...</div>
    `;
    }
    ...
});
Enter fullscreen mode Exit fullscreen mode

This is the default pattern introduced by many famous web component documentations. It's the easiest way to jump straight to use Shadow DOM in Web Component, but it cause many following complicatations.

  1. It separates DOM access between Host and Shadow DOM
  2. It separated CSS application from Host and Shadow DOM

Image description

This is where the complication start. When we use Shadom DOM like this in web component, we have to think from the start if the component will contain shadowRoot or not. Then it separate Web Component into 2 types, "Shadow" or "Light" DOM
Which also brings a lot of awkward way to implement component methods and CSS.

When you want to access child nodes from Shadow Host, you have to access shadowRoot. When you want to apply css on host node, you have to use :host {}. This add an extra layer to program or style component which doesn't exist in normal components.

Does it have to be like that ? On my second thought, I come up with another solution.

Let's use Shadow DOM in the right way ?
You can read my solution above again.

Top comments (0)