DEV Community

Cover image for Exploring CSS Shadow DOM and CSS Custom Shadow Parts
Sharique Siddiqui
Sharique Siddiqui

Posted on

Exploring CSS Shadow DOM and CSS Custom Shadow Parts

Modern web development emphasizes building reusable, encapsulated components—a goal the Shadow DOM and CSS Custom Shadow Parts directly address. These powerful browser features enable developers to isolate component internals from the global page, avoiding style leakage and conflicts, while still allowing controlled customization.

This post explores what Shadow DOM is, how CSS encapsulation works inside it, and how CSS Custom Shadow Parts allow safe styling of internal component elements from outside.

What is Shadow DOM?

Shadow DOM is a web standard that lets you create a separate, encapsulated DOM subtree for an element, known as a shadow tree. This subtree's markup and styling are isolated from the main document DOM, preventing unintended interference. It’s the foundational technology behind Web Components.

Key Features of Shadow DOM
  • Encapsulation: Internal structure and styles are hidden from the main document and external CSS.
  • Scoped Styles: Styles inside the shadow tree only affect its nodes, and page styles cannot bleed in.
  • Shadow Host: The regular DOM element that hosts the shadow tree.
  • Shadow Root: The root node of the shadow tree, created via JavaScript with attachShadow() or declaratively through <template shadowrootmode="open|closed">.
Example: Creating Shadow DOM with JavaScript
js
const host = document.querySelector('#host');
const shadow = host.attachShadow({ mode: 'open' });

const span = document.createElement('span');
span.textContent = "I'm inside the shadow DOM!";
shadow.appendChild(span);
Enter fullscreen mode Exit fullscreen mode

In this example, the <span> lives in the shadow DOM inside the host element, isolated from styles and markup outside the shadow root.

Why Encapsulation Matters

Without Shadow DOM, styles from the outer page can accidentally override or interfere with a component’s internal styles, and vice versa—this makes maintaining large apps complicated. Shadow DOM eliminates this by creating a "style and markup bubble" around components, ensuring they behave predictably regardless of the surrounding page.

Styling Inside the Shadow DOM

Since styles are scoped inside the shadow root, you style shadow DOM elements internally with regular CSS placed inside the shadow tree:

js
shadow.innerHTML = `
  <style>
    span {
      color: red;
      border: 1px solid black;
    }
  </style>
  <span>Shadow DOM content</span>
`;
Enter fullscreen mode Exit fullscreen mode

This CSS will only apply to elements inside the shadow tree, preventing conflicts with outside styles.

What are CSS Custom Shadow Parts?

While Shadow DOM provides encapsulation, it also restricts customizing internal elements of a component from outside—sometimes you want to expose certain parts for styling without breaking encapsulation.

CSS Custom Shadow Parts allow web components to define which internal elements are styleable by the outside world.

How to Use Shadow Parts
1.Inside the shadow DOM, add a part attribute to elements you want to expose:
xml
<button part="button">Click me</button>
Enter fullscreen mode Exit fullscreen mode
2.Outside the shadow host, style these parts using the ::part() pseudo-element:
css
my-component::part(button) {
  background-color: green;
  border-radius: 5px;
}
Enter fullscreen mode Exit fullscreen mode

This approach safely exposes only specific internal elements for styling, preserving encapsulation while giving flexible customization options.

Practical Example of Shadow DOM with Custom Parts

js
class MyButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' }).innerHTML = `
      <style>
        button {
          background-color: blue;
          color: white;
          padding: 10px;
          border: none;
          cursor: pointer;
        }
      </style>
      <button part="button">Press me</button>
    `;
  }
}
customElements.define('my-button', MyButton);
Enter fullscreen mode Exit fullscreen mode

Then, from outside:

css
my-button::part(button) {
  background-color: orange; /* Override internal button style */
}
Enter fullscreen mode Exit fullscreen mode

The external CSS targets the inner button’s part="button" while the rest of the component remains encapsulated.

Feature and description

Feature Description
Shadow DOM Encapsulates markup and styles inside a shadow root, isolating from page styles.
Shadow Host Regular DOM element hosting the shadow root.
Scoped Styles Styles inside shadow root apply only within that tree.
CSS Custom Shadow Parts Provides a sanctioned way for external styling of internal component parts.
part attribute Marks internal elements to be exposed for external styling.
::part() selector Targets parts from outside the shadow root for custom styling.

By using Shadow DOM and CSS Custom Shadow Parts, developers create robust, maintainable web components that are simultaneously encapsulated and customizable—ushering in a new era of reusable UI building blocks.

Stay tuned for more insights as you continue your journey into the world of web development!

Check out the YouTube Playlist for great CSS content for basic to advanced topics.

Please Do Subscribe Our YouTube Channel for clearing programming concept and much more ...CodenCloud

Top comments (1)

Collapse
 
dannyengelman profile image
Danny Engelman

You chain innerHTML! That is what most developers don't know

constructor() {
    super();
    this.attachShadow({ mode: 'open' }).innerHTML = "..."
Enter fullscreen mode Exit fullscreen mode

you can also chain super()

constructor() {
    super() // sets AND returns 'this' scope
        .attachShadow({ mode: 'open' }) // sets AND returns this.shadowRoot
        .innerHTML = "..."
Enter fullscreen mode Exit fullscreen mode