DEV Community

Cover image for Understanding Shadow DOM by Building a Poll Plugin
Mehak.
Mehak.

Posted on

Understanding Shadow DOM by Building a Poll Plugin

🌐 What is Shadow DOM?

Shadow DOM is a browser feature that allows us to attach a separate DOM tree to an element, a tree that’s isolated from the rest of the page.

Think of it as a "mini web page" inside an element, with its own HTML, CSS, and JS completely encapsulated from the rest of the site.


πŸ”‘ Key Concepts

  • Shadow Host: The DOM element where the Shadow DOM is attached.
  • Shadow Root: The root of the shadow tree, created with element.attachShadow(options).
  • Shadow Boundary: The barrier between the shadow DOM and light DOM (regular DOM). Styles and events generally don’t cross this boundary unless configured.

🧡 Modes: open vs closed

You can attach a shadow root in two modes:

  • open: The shadow root is accessible via element.shadowRoot.
  • closed: The shadow root is not accessible from the outside.

πŸ›‘ Note: Even in open mode, accessing shadow DOM externally is discouraged as it breaks encapsulation.


πŸ” Communicating with the Light DOM

Since shadow DOM is isolated, direct interaction with outside elements is limited. The recommended way to communication is using Custom Events.

const voteEvent = new CustomEvent('simplepoll-vote', {
  detail: { vote: 'React' },
  bubbles: true,
  composed: false // set true if event is dispatch within the shadow boundary.
});
host.dispatchEvent(voteEvent);
Enter fullscreen mode Exit fullscreen mode

⚠️ Without composed: true, the event will not escape the shadow DOM and cannot be listened to in the main document.

πŸ“ Important Clarification

If you dispatch the event from the shadow root, composed: true is necessary to make it cross the boundary.

However, if you dispatch the event from the host element, composed is not needed as it’s already outside the shadow tree.


πŸ§ͺ Let's Build a Real Plugin: Poll Widget

To reinforce our understanding, I built a simple poll plugin using Shadow DOM. This plugin:

  • Creates a styled voting widget inside a Shadow DOM
  • Encapsulates its layout and logic
  • Dispatches a custom event on vote
  • Can be embedded into any website

πŸ—‚ Folder Structure

poll-plugin/
β”œβ”€β”€ index.js          # Entry point (exports the class)
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ dom.js        # DOM creation helpers
β”‚   β”œβ”€β”€ template.js   # HTML content generator
β”‚   β”œβ”€β”€ style.js      # CSS as JS string
β”‚   └── logic.js      # Voting logic and event binding
β”œβ”€β”€ index.html        # to test the plugin
β”œβ”€β”€ vite.config.js    # build config
β”œβ”€β”€ package.js 

Enter fullscreen mode Exit fullscreen mode

βš™οΈ How It Works

const container = createContainer('poll-container');
const shadow = container.attachShadow({ mode: 'closed' });
shadow.appendChild(style);
shadow.appendChild(wrapper);
Enter fullscreen mode Exit fullscreen mode
  • The poll UI is created inside the shadow root.
  • Created the poll in closed mode so main dom cannot access it.
  • Styles are injected with a style tag.
  • The HTML is dynamically created from config.
  • When a user votes, we emit a custom event which will listen by main dom.

πŸ’¬ Listening for Poll Votes

To listen event in the main dom:

 document.addEventListener('vote-submitted', (e) => {
        alert(`You voted: ${e.detail.vote}`);
  });
Enter fullscreen mode Exit fullscreen mode

This keeps your main app clean while the logic is self-contained.


πŸ›  Bundling with Vite

To make this plugin usable in any website, I used Vite as it's a fast, modern frontend build tool that simplifies development and bundling.Vite allows us to:

  • Write modular, clean source code in separate files
  • Use modern JavaScript or TypeScript
  • Compile everything into a single minified JS file ready for production

πŸ”— How to Use It in Any Website

After bundling the plugin we can easily embedd to any website using script tag.

 <script src="./dist/poll.iife.js"></script>
    <script>
      Poll.init({
        question: 'What is your favorite JS framework?',
        options: ['React', 'Vue', 'Svelte', 'Solid']
      });

      document.addEventListener('vote-submitted', (e) => {
        alert(`You voted: ${e.detail.vote}`);
      });
    </script>
Enter fullscreen mode Exit fullscreen mode

It works ansywhere without leaking styles or affecting the main page.


🧾 Full Source Code

https://github.com/MehakB7/poll-plugin

Top comments (0)