The Shadow DOM is a key feature of Web Components, enabling developers to create encapsulated DOM trees and style scopes. This guide is designed to provide a deep understanding of the Shadow DOM, along with practical code examples to demonstrate its use.
What is Shadow DOM?
Shadow DOM allows developers to encapsulate a part of the DOM and its styles, isolating it from the rest of the document. This ensures that styles and scripts within the Shadow DOM do not clash with those in the main document.
<body>
<div id="host"></div>
<script>
// Create a shadow root
const hostElement = document.getElementById('host');
const shadowRoot = hostElement.attachShadow({ mode: 'open' });
// Attach shadow DOM content
shadowRoot.innerHTML = `
<style>
.shadow-box {
padding: 10px;
border: 1px solid #000;
background-color: lightblue;
}
</style>
<div class="shadow-box">Hello, Shadow DOM!</div>
`;
</script>
</body>
Creating a Shadow Root
To create a shadow root, use the attachShadow
method on an element. The shadow root can be either open
or closed
. An open
shadow root is accessible from JavaScript outside the shadow tree, while a closed
shadow root is not.
Open Shadow Root
An open shadow root allows access and manipulation from external JavaScript. In the example below, we manipulate the text content inside the shadow root after the shadow root is created.
<body>
<div id="open-shadow-host"></div>
<button id="open-shadow-btn">Change Shadow Content</button>
<script>
const openShadowHost = document.getElementById('open-shadow-host');
const openShadowRoot = openShadowHost.attachShadow({ mode: 'open' });
openShadowRoot.innerHTML = `
<style>
.shadow-content {
color: blue;
padding: 10px;
border: 1px solid black;
}
</style>
<div class="shadow-content">This is an open shadow root</div>
`;
document.getElementById('open-shadow-btn').addEventListener('click', () => {
openShadowRoot.querySelector('.shadow-content').textContent = 'Open Shadow Root content updated!';
});
</script>
</body>
Closed Shadow Root
A closed shadow root restricts access from external scripts, providing better encapsulation. In the example below, we try to manipulate the text content inside the shadow root after the shadow root is created, but it is not possible since it is closed
.
<body>
<div id="closed-shadow-host"></div>
<button id="closed-shadow-btn">Try to Change Shadow Content</button>
<script>
const closedShadowHost = document.getElementById('closed-shadow-host');
const closedShadowRoot = closedShadowHost.attachShadow({ mode: 'closed' });
closedShadowRoot.innerHTML = `
<style>
.shadow-content {
color: red;
padding: 10px;
border: 1px solid black;
}
</style>
<div class="shadow-content">This is a closed shadow root</div>
`;
// This will not work as intended because the shadow root is closed
document.getElementById('closed-shadow-btn').addEventListener('click', () => {
try {
closedShadowHost.shadowRoot.querySelector('.shadow-content').textContent = 'Attempted to update closed shadow root!';
} catch (e) {
alert('Cannot access shadow root content from outside!');
}
});
</script>
</body>
Styling within Shadow DOM
Styles defined within a shadow root do not affect elements outside it, and vice versa. This encapsulation is beneficial for creating reusable components.
<div id="styled-host"></div>
<script>
const styledHost = document.getElementById('styled-host');
const shadowRoot = styledHost.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
.shadow-box {
color: white;
background-color: #333;
padding: 10px;
border-radius: 5px;
}
</style>
<div class="shadow-box">Styled Shadow DOM</div>
`;
</script>
Slotting: Light DOM Content in Shadow DOM
Slots allow developers to pass light DOM (regular DOM) content into a shadow DOM, making the shadow DOM more flexible and reusable.
<div id="slot-host">
<span slot="title">Shadow DOM Slot Example</span>
</div>
<script>
const slotHost = document.getElementById('slot-host');
const shadowRoot = slotHost.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
.container {
border: 1px solid #ccc;
padding: 10px;
}
</style>
<div class="container">
<h1><slot name="title"></slot></h1>
<p>This is a Shadow DOM component with a slot for the title.</p>
</div>
`;
</script>
JavaScript Interaction with Shadow DOM
Interacting with the Shadow DOM via JavaScript requires understanding the encapsulation boundaries. Direct manipulation within the shadow root is straightforward, but external interaction requires careful handling.
Accessing Shadow DOM Elements
To access elements within a shadow DOM, use the shadowRoot
property.
<div id="interactive-host"></div>
<script>
const interactiveHost = document.getElementById('interactive-host');
const shadowRoot = interactiveHost.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<button id="shadow-btn">Click me</button>
`;
const shadowButton = shadowRoot.getElementById('shadow-btn');
shadowButton.addEventListener('click', () => {
alert('Button inside Shadow DOM clicked!');
});
</script>
Shadow DOM Practical Examples
Creating a Reusable Web Component
Creating a reusable web component using Shadow DOM involves defining a custom element and attaching a shadow root to it.
<body>
<custom-card title="Hello World"></custom-card>
<script>
class CustomCard extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
.card {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.card-title {
font-size: 1.2em;
margin-bottom: 5px;
}
</style>
<div class="card">
<div class="card-title">${this.getAttribute('title')}</div>
<div class="card-content"><slot></slot></div>
</div>
`;
}
}
customElements.define('custom-card', CustomCard);
</script>
</body>
Integrating with Frameworks
Shadow DOM can be used seamlessly with modern JavaScript frameworks like React, Angular, and Vue.
React Example
In React, you can create a custom element and integrate it with Shadow DOM as follows:
import React, { useEffect } from 'react';
const CustomCard = ({ title, children }) => {
useEffect(() => {
class CustomCardElement extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
.card {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.card-title {
font-size: 1.2em;
margin-bottom: 5px;
}
</style>
<div class="card">
<div class="card-title">${this.getAttribute('title')}</div>
<div class="card-content"><slot></slot></div>
</div>
`;
}
}
customElements.define('custom-card', CustomCardElement);
}, []);
return <custom-card title={title}>{children}</custom-card>;
};
export default CustomCard;
Conclusion
Mastering Shadow DOM is essential for modern web development, providing powerful encapsulation and reusability. By understanding and implementing the concepts and examples provided, you can create robust, isolated components that enhance the maintainability and scalability of your web applications.
This comprehensive guide should serve as a solid foundation for exploring and utilizing Shadow DOM in your projects. Whether you are building simple widgets or complex applications, the Shadow DOM offers the encapsulation and flexibility needed to ensure your components remain isolated and manageable.
Practice Your Knowledge
Quiz Time: Test Your Skills!
Ready to challenge what you've learned? Dive into our interactive quizzes for a deeper understanding and a fun way to reinforce your knowledge.