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 DOM
separates style and functionality, preventing conflicts with global styles and scripts.
- The
-
Reusability
- Components built with the
Shadow DOM
can 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
open
mode, external JavaScript can access the shadow root viaelement.shadowRoot
. On the other hand,closed
mode 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 DOM
remains 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 DOM
bubbles up to theLight DOM
, the event'starget
is rewritten to the host element instead of the original source.- In this example,
event.target
inside theShadow DOM
is the actualbutton
element, but outside theShadow DOM
, it is replaced with the hostdiv
element.
- 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 DOM
inside custom elements, you can build reusable components that are resistant to style conflicts. In this code, a tag namedmy-card
is created and associated with theMyCard
class. The<p>
element insidemy-card
is 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
slot
element inside theShadow DOM
displaysLight DOM
content that has the correspondingslot
attribute.
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.