Introduction to Shadow DOM

Introduction to Shadow DOM

Shadow DOM is an emerging web standard that gives developers access to style and DOM scoping. Learn how to use it on your own website.


What is Shadow DOM?

HTML5 video tag

Here's a video rendered in the browser using the HTML video tag. While the code is as simple as a single tag, the video has built-in controls.

<video src="http://craftymind.com/factory/html5video/BigBuckBunny_640x360.mp4" controls></video>

If you open up the DevTools and turn on 'Show user agent shadow DOM', we can actually look at the source for the video controls.

Show user agent shadow DOM

And you'll see that they're actually made of HTML. This is an example of using the Shadow DOM.

Shadow DOM in DevTools

The nice thing about Shadow DOM is that you can actually use this feature in your own components.

Structure of a Shadow DOM

An element that has a shadow root associated with it is called "shadow host". The shadow root can be treated as an ordinary DOM element so you can append arbitrary nodes to it.

Structure of a Shadow DOM

With Shadow DOM, all markup and CSS are scoped to the host element. In other words, CSS styles defined inside a Shadow Root won't affect its parent document, CSS styles defined outside the Shadow Root won't affect the main page.

How I build a Shadow DOM

In order to create a Shadow DOM, invoke .createShadowRoot() on a DOM node and obtain a Shadow Root. By adding elements to the Shadow Root, you can build Shadow DOM.

<div id="host"></div>
var host = document.querySelector('#host');
var root = host.createShadowRoot(); // Create a Shadow Root
var div = document.createElement('div');
div.textContent = 'This is Shadow DOM';
root.appendChild(div); // Append elements to the Shadow Root

Notice that elements added to the Shadow Root won't be queried. In this case document.querySelector('#host div') results in null.

Reflecting the Shadow Host's content to a Shadow DOM

Sometimes you may want to project the child elements of a Shadow Host into a Shadow DOM.

Imagine you want the similar functionality to the combination of <select> and <option>. They are separate tags but make sense as a select menu when used together.

With Shadow DOM, you can do this for example: A name tag that is styled in the Shadow DOM, but needs to pull in the user's name from an external input.

Shadow DOM name tag example

<div id="nameTag">Bob</div>

In order to achieve this, you can use <content> element inside the Shadow DOM.

var host = document.querySelector('#host');
var root = host.createShadowRoot();
var content = document.createElement('content');
content.setAttribute('select', 'h1'); // <content select="h1"></content>
root.appendChild(content);
<div id="host">
  <h1>This is Shadow DOM</h1>
<div>

By giving <content> tag a select attribute with CSS selector as a value, you can distribute host's content to wherever you want.

Note that select attribute can only take direct children of the host element. For example, you can NOT assign descendant elements to the select attribute:

<div id="host">
  <div class="child">
    <h1>This is Shadow DOM</h1>
  </div>
</div>

<content select=".child h1"></content> // Not allowed

Combining with Templates

Shadow DOM is fantastic as you have learned so far, but adding contents imperatively isn't that efficient and is not designer friendly. Instead, you may wish to use HTML to define your content.

Here's where the <template> element comes in. Using the template element, you can define contents of your Shadow DOM declaratively with HTML. To learn about the <template> element, check out the previous post.

<!-- Content of <template> will be appended to the Shadow Root -->
<template id="template">
  <style>
    ...
  </style>
  <div id="container">
    <img src="http://webcomponents.org/img/logo.svg">
    <content select="h1"></content> // Insert h1 here
  </div>
</template>

<div id="host">
  <h1>This is Shadow DOM</h1>
</div>
var host = document.querySelector('#host');
// Create a Shadow Root
var root = host.createShadowRoot();
var template = document.querySelector('#template');
// Copy the <template>
var clone = document.importNode(template.content, true);
// Append template to the Shadow Root
root.appendChild(clone);

Here's a live example.

Supported browsers

Shadow DOM is supported by Chrome and Opera. Firefox supports it behind a flag as of October 2014. To check availability, go to caniuse.com. For polyfilling other browsers, you can use platform.js (renamed as webcomponents.js in Nov. 2014).

Resources

So that is the very basic of the Shadow DOM. But this is only the tip of iceberg. There's tons of interesting things to learn around Shadow DOM such as

  • Styling
  • Event handling
  • Working with multiple Shadow Roots

If you are interested in learning them, check out following pages:

Head to posts tagged with Shadow DOM to learn even more.

Tags