Shadow DOM and Custom Elements Explained

Web Components are a relatively new set of technologies that lets you create custom HTML elements using the shadow DOM. This enables you to write markup, styles and logic once and reuse it around your website or web app! In this article, we are going to look into the technologies which power Web Components and create a custom HTML element from scratch!

Shadow DOM… What is DOM?

For the uninitiated, DOM stands for Document Object Model. It is a tree-like structure generated by a web browser like Google Chrome to represent the contents of a web page.

Example

Consider the following HTML document:

<!DOCTYPE html>
<html>
  <head>
    <title>An Example HTML Document</title>
  </head>
  <body>
    <h1>Yay</h1>
  </body>
</html>

This document when represented as the DOM, looks something like this:

An Example of the Document Object Model (DOM) - GeekyMinds

More from GeekyMinds: Progressive Web Apps Made Easy

Shadow DOM – The Most Simple Explanation

Let’s consider the HTML <video> tag for a moment. We use it to put videos on our websites for the world to see. But have you considered the fact that you did not need to write any markup or styling for the video player?

The markup, styles, etc associated with the <video> tag are hidden away from you thus keeping your code nice and clean. This is shadow DOM, the regular Document Object Model, whose markup, styles and behavior are kept encapsulated so that they don’t clash with existing code on the page.

Can you think of any other HTML tag that uses shadow DOM? Comment down below!

Keep in mind, the shadow DOM is just like the regular DOM under the hood. Therefore, it fully supports the DOM API. Using it, you can create custom elements which can be reused in your app’s user interface.

The markup is defined once along with the styling and scripting. Since shadow DOM is rendered separately, there’s no chance of your code colliding with other code in the page.

Shadow DOM Terminology

Now let’s go a bit deeper and learn about a few important terminologies!

Shadow Host – Remember, the Shadow DOM is a DOM tree by itself. If you want to use it in your document, you’ll have to attach it to your regular DOM. The shadow host is the node that the shadow DOM is attached to.

Shadow Tree – The tree inside the shadow DOM is known as the shadow tree. Easy!

Shadow Root – The root node of the shadow tree. The entire shadow tree is attached to or hangs from, this node.

A diagrammatic representation of the Shadow DOM. Source: MDN

Custom Elements

Custom Elements are one of the key features of Web Components. As stated before, it lets you write code once and reuse it in other places. Custom elements can inherit from existing native HTML elements if you require it.

This gives us two types of custom elements:

1. Autonomous custom elements

  • They do not inherit from existing elements
  • Usage: <custom-element></custom-element>

2. Customized built-in elements

  • These inherit from existing HTML elements
  • Usage: <p is="custom-element"></p>

When you inherit from a native HTML element, you cannot use the custom element’s name directly. For example, if we make a custom-element which inherits from <p>, then the above example will suffice.

Building A Custom Element From Scratch

Okay, enough theory. The best way to understand shadow DOM is by creating a custom element. To keep the article from getting too long, we are only going to make an autonomous custom element. Let’s get coding!

Todo List UI Using Custom Elements

Building a Todo List UI using custom HTML elements and shadow DOM - GeekyMinds
The Todo List app UI

Here you can see the Todo list app UI we are going to build. The tasks you see listed are built using a custom element called <todo-item> which has a text attribute.

Point to note: The custom element tag needs to be in kebab-case. This is compulsory.

More from GeekyMinds: JavaScript Promises Explained!

Here Are The Steps

  1. Write the custom element
  2. Register the custom element
  3. Use it in our markup

Writing the custom element

In order to write the custom element, we will need to define a class for it. By convention, use PascalCase for naming the class. Do not forget to extend HTMLElement.

We are going to use two new methods: Element.attachShadow() and customElements.define(). I have explained them right after the script below!

The rest is documented using comments and should be fairly self-explanatory. If you have doubts, do not hesitate to use the comment section below.

Here’s The Script

class TodoItem extends HTMLElement {
 constructor() {
  // Always call super first
  super();

  // Create a shadow root
  const shadow = this.attachShadow({ mode: "open" });

  // Create <div class="flex"></div>
  const div = document.createElement("div");
  div.setAttribute("class", "flex");

  // Write some CSS to apply to the shadow DOM
  const style = document.createElement("style");
  style.textContent = `
    // Omitted the styles to keep code short
    // Refer to CodePen below for full code 
  `;

  // Get todo text
  let text;
  if (this.hasAttribute("text")) {
   text = this.getAttribute("text");
  } else {
   text = null;
   console.error("Error: text attribute missing.");
  }

  // Set div innerHTML
  div.innerHTML = `
   <div class="text">${text}</div>
   <div class="btn">✖</div>
  `;
  
  // Attach the created elements to the shadow DOM
  shadow.appendChild(style);
  shadow.appendChild(div);
 }
}

// Define the new element
customElements.define('todo-item', TodoItem);

Element.attachShadow()

As stated above in the terminology section, we will need to attach the shadow DOM tree we create to the document using an existing element of the DOM. This is done using the Element.attachShadow() method.

The method takes one parameter, an options object, containing one key called mode. It can have two values: open or closed. Setting the mode to open will allow accessing the shadow DOM from JavaScript written in the main document.

let shadow = elementRef.attachShadow({mode: 'open'});
let shadow = elementRef.attachShadow({mode: 'closed'});

customElements.define()

The define() method of the CustomElementRegistry interface allows us to register the custom element we create. It takes two compulsory parameters: the custom element tag and the constructor for the custom element. Easy peasy!

customElements.define('todo-item', TodoItem);

Here’s The Markup

Notice how the custom element has been used by its tag!

<div class="todo-list">
 <div class="todo-header">Todo List</div>
 <todo-item text="Take dog out for a walk"></todo-item>
 <todo-item text="Get dog food on the way back"></todo-item>
 <todo-item text="Feed and cuddle with dog"></todo-item>
</div>

More from GeekyMinds: Difference b/w Websites and Web Apps

The Final Source Code

See the Pen Todo List Using Custom Elements and Shadow DOM by Aritra Mukherjee (@mukherjee96) on CodePen.

Remember, the logic and styling belonging to the custom element are part of the shadow DOM and therefore encapsulated. None of the styles you write for a custom element will interfere with other parts of the user interface.

That’s all folks! Comment down below if you have doubts. We are more than happy to help! 😃