Shadow DOM in JavaScript
This article explains the Shadow DOM in JavaScript.
YouTube Video
javascript-html-shadow-dom.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>JavaScript & HTML</title>
6 <style>
7 * {
8 box-sizing: border-box;
9 }
10
11 body {
12 margin: 0;
13 padding: 1em;
14 padding-bottom: 10em;
15 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16 background-color: #f7f9fc;
17 color: #333;
18 line-height: 1.6;
19 }
20
21 .container {
22 max-width: 800px;
23 margin: 0 auto;
24 padding: 1em;
25 background-color: #ffffff;
26 border: 1px solid #ccc;
27 border-radius: 10px;
28 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
29 }
30
31 h1, h2 {
32 font-size: 1.2rem;
33 color: #007bff;
34 margin-top: 0.5em;
35 margin-bottom: 0.5em;
36 border-left: 5px solid #007bff;
37 padding-left: 0.6em;
38 background-color: #e9f2ff;
39 }
40
41 button {
42 display: block;
43 margin: 1em auto;
44 padding: 0.75em 1.5em;
45 font-size: 1rem;
46 background-color: #007bff;
47 color: white;
48 border: none;
49 border-radius: 6px;
50 cursor: pointer;
51 transition: background-color 0.3s ease;
52 }
53
54 button:hover {
55 background-color: #0056b3;
56 }
57
58 #output {
59 margin-top: 1em;
60 background-color: #1e1e1e;
61 color: #0f0;
62 padding: 1em;
63 border-radius: 8px;
64 min-height: 200px;
65 font-family: Consolas, monospace;
66 font-size: 0.95rem;
67 overflow-y: auto;
68 white-space: pre-wrap;
69 }
70
71 .highlight {
72 outline: 3px solid #ffc107; /* yellow border */
73 background-color: #fff8e1; /* soft yellow background */
74 transition: background-color 0.3s ease, outline 0.3s ease;
75 }
76
77 .active {
78 background-color: #28a745; /* green background */
79 color: #fff;
80 box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
81 transition: background-color 0.3s ease, box-shadow 0.3s ease;
82 }
83 </style>
84</head>
85<body>
86 <div class="container">
87 <h2>HTML Sample</h2>
88 <div id="content"></div>
89 <div id="shadow-host">Shadow Root Element</div>
90 <my-card></my-card>
91 </div>
92
93 <div class="container">
94 <h1>JavaScript Console</h1>
95 <button id="executeBtn">Execute</button>
96 <div id="output"></div>
97 </div>
98
99 <div>
100 <h2>Slot Sample</h2>
101 <my-element>
102 <h3 slot="header">Header Content</h1>
103 <p slot="content">Main Content</p>
104 </my-element>
105 </div>
106
107 <script>
108 // Override console.log to display messages in the #output element
109 (function () {
110 const originalLog = console.log;
111 console.log = function (...args) {
112 originalLog.apply(console, args);
113 const output = document.getElementById('output');
114 output.textContent += args.map(String).join(' ') + '\n';
115 };
116 })();
117
118 document.getElementById('executeBtn').addEventListener('click', () => {
119 // Prevent multiple loads
120 if (document.getElementById('externalScript')) return;
121
122 const script = document.createElement('script');
123 script.src = 'javascript-html-shadow-dom.js';
124 script.id = 'externalScript';
125 //script.onload = () => console.log('javascript-html-shadow-dom.js loaded and executed.');
126 //script.onerror = () => console.log('Failed to load javascript-html-shadow-dom.js.');
127 document.body.appendChild(script);
128 });
129 </script>
130</body>
131</html>Understanding Shadow DOM
Shadow DOM is a powerful feature of the Web Components standard that enables encapsulation of styles and DOM structure within components. This feature prevents interference of styles and scripts between components and the main document.
Shadow DOM in JavaScript
Shadow DOM provides a way to create a scoped DOM tree associated with a regular DOM element. This shadow tree is isolated from the overall document, where external styles and scripts do not influence it, nor do its internal styles and scripts leak out.
For example, if you create a custom button component using Shadow DOM, its styles will not interfere with other elements on the page. Similarly, elements with the same class name will not conflict.
The regular HTML content outside of the Shadow DOM is referred to as the Light DOM.
Benefits of Shadow DOM
-
Encapsulation
- The
Shadow DOMseparates style and functionality, preventing conflicts with global styles and scripts.
- The
-
Reusability
- Components built with the
Shadow DOMcan be reused across different projects without worrying about style conflicts.
- Components built with the
-
Maintainability
- Encapsulation makes the component's logic and style self-contained, making debugging and maintenance easier.
Creating Shadow DOM
To use Shadow DOM, you need to attach a shadow root to an HTML element. Here is a simple example:.
1// Select the host element
2const hostElement = document.querySelector('#shadow-host');
3
4// Attach a shadow root
5const shadowRoot = hostElement.attachShadow({ mode: 'open' });Explanation
This code includes the following elements:.
-
Host Element
- A regular DOM element to which the shadow root is attached (in this case,
#shadow-host).
- A regular DOM element to which the shadow root is attached (in this case,
-
Shadow Root
- The root of the shadow tree created using
attachShadow.
- The root of the shadow tree created using
-
Mode
- In
openmode, external JavaScript can access the shadow root viaelement.shadowRoot. On the other hand,closedmode does not allow access.
- In
Styling within Shadow DOM
Shadow DOM has its own style scope. Styles defined within the shadow tree apply only to the elements within that tree. Here is an example:.
1// Add content to the shadow root
2shadowRoot.innerHTML = `
3 <style>
4 p {
5 color: green;
6 }
7 </style>
8 <p>Scoped style inside Shadow DOM.</p>
9`;Even if there are conflicting styles in the main document, they do not affect the paragraph inside the shadow tree.
1const content = document.getElementById('content');
2content.innerHTML = `
3 <style>
4 p {
5 color: red;
6 }
7 </style>
8 <p>This is in the main DOM.</p>
9`;- The paragraph inside the
Shadow DOMremains green, while the external one is red.
Events within the Shadow DOM
Events within the Shadow DOM are similar to regular DOM events but may behave differently in terms of propagation due to encapsulation.
Here is an example:.
1// Add an event listener inside Shadow DOM
2shadowRoot.innerHTML = `
3 <div id="button-container">
4 <button id="shadow-button">Click Me</button>
5 </div>
6`;
7
8shadowRoot.querySelector('#shadow-button').addEventListener('click', (event) => {
9 console.log('Button : Button clicked inside Shadow DOM');
10 console.log(event.target);
11});
12
13shadowRoot.querySelector('#button-container').addEventListener('click', (event) => {
14 console.log('Container : Button clicked inside Shadow DOM');
15 console.log(event.target);
16});
17
18hostElement.addEventListener('click', (event) => {
19 console.log('Event bubbled to the host element');
20 console.log(event.target);
21});
22
23document.addEventListener('click', (event) => {
24 console.log('Document listener');
25 console.log(event.target);
26});- When the button is clicked, both listeners are triggered, demonstrating the behavior of event bubbling.
- When an event originating inside the
Shadow DOMbubbles up to theLight DOM, the event'stargetis rewritten to the host element instead of the original source.- In this example,
event.targetinside theShadow DOMis the actualbuttonelement, but outside theShadow DOM, it is replaced with the hostdivelement.
- In this example,
Shadow DOM and Custom Elements
Shadow DOM and custom elements are key components of Web Components. They are technologies used to create reusable and encapsulated UI components.
1class MyCard extends HTMLElement {
2 constructor() {
3 super();
4 const shadow = this.attachShadow({ mode: 'open' });
5 shadow.innerHTML = `
6 <style>
7 p {
8 color: blue;
9 }
10 </style>
11 <p>I'm inside shadow DOM</p>
12 `;
13 }
14}
15
16customElements.define('my-card', MyCard);The main document contains HTML like this:.
1<my-card></my-card>- By using
Shadow DOMinside custom elements, you can build reusable components that are resistant to style conflicts. In this code, a tag namedmy-cardis created and associated with theMyCardclass. The<p>element insidemy-cardis not affected by external styles and is always displayed in blue.
Slots: Distributing Light DOM Content
Slots allow you to project Light DOM content into the Shadow DOM. Here is an example:.
1class MyElement extends HTMLElement {
2 constructor() {
3 super();
4 const shadow = this.attachShadow({ mode: 'open' });
5
6 shadow.innerHTML = `
7 <style>
8 .container {
9 border: 2px solid #ccc;
10 padding: 16px;
11 border-radius: 8px;
12 font-family: sans-serif;
13 }
14 .header {
15 font-size: 1.2em;
16 color: darkblue;
17 margin-bottom: 8px;
18 }
19 .content {
20 font-size: 1em;
21 color: #333;
22 }
23 </style>
24 <div class="container">
25 <div class="header">
26 <slot name="header"></slot>
27 </div>
28 <div class="content">
29 <slot name="content"></slot>
30 </div>
31 </div>
32 `;
33 }
34}
35
36customElements.define('my-element', MyElement);The main document contains HTML like this:.
1<my-element>
2 <h3 slot="header">Header Content</h1>
3 <p slot="content">Main Content</p>
4</my-element>- The
slotelement inside theShadow DOMdisplaysLight DOMcontent that has the correspondingslotattribute.
Conclusion
Shadow DOM is a vital tool for building robust, reusable, and maintainable web components. By encapsulating styles and functionality, it reduces the potential for conflicts and simplifies codebase management.
You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.