One way of fixing this problem is through the Shadow DOM. 

Shadow DOM works by introducing scoped styles. It does not require any special naming conventions or tools. Bundling CSS with markup becomes simple with Shadow DOM. Also, this capability allows us to hide all details about implementation in vanilla JavaScript.

Shadow DOM provides the following solutions:

To demonstrate Shadow DOM, we are going to use a simple component called tuts-tabs. All references in this post will point to this piece of code. To experience the Shadow DOM, just take a look at the demo below:

Before you start to code with the Shadow DOM, you need to understand the regular DOM. 

HTML acts as the backbone of a web site. In just a few minutes you can create a page. When you open that page in a browser, the DOM starts to come into play. Once the browser loads a page, it starts to parse HTML into a data model. This data model is a tree structure with nodes. These nodes represent the elements in your HTML. This data model is easy to modify and manipulate with code. 

The downside is that your entire web page or even complex web application is treated as a single data structure. This is not very easy to debug! For example, CSS styles that are intended for one component can end up affecting another component elsewhere in your app.

When you want to isolate one part of your interface from the rest, you can use iframes. But, iframes are heavy, and extremely restrictive.

That’s why Shadow DOM was introduced. It is a powerful capability of modern browsers that allows web devlopers to include subtree of various elements in the DOM. These subtrees of the DOM don’t affect the main document tree. Technically, these are known as a shadow tree

The shadow tree has a shadow root which gets attached to a parent in the DOM. This parent is known as the shadow host

For example, if you have <input type="range"> plugged into a browser that is powered by WebKit, it will translate to a slider. Why? This is a slider because one of the subtree DOM element understands the “range” to change its appearance and introduce slider-like functionalities. This is an advantage Shadow DOM brings to the tab. 

Use element.attachShadow() to create a Shadow DOM element. 

In our example, tuts-tab, you will see this link of code for creating the Shadow DOM element. 

Next, we will add content to the shadow root using .innerHTML. Note that this is not the only way of populating your Shadow DOM. There are many APIs to help you populate the Shadow DOM. 

Connecting custom elements to the shadow DOM is extremely simple. Remember, when you combine custom elements with the Shadow DOM, you will be able to create encapsulated components with CSS, JavaScript and HTML. As a result, you will create a brand new web component that can be reused across your application. 

As we extend HTMLElement, it is important to call super() inside the constructor. Also, the constructor is where the shadowRoot needs to be created. 

Once the shadowRoot is created, you can create CSS rules for it. The CSS rules can be enclosed in the <style> tag, and these styles will be only scoped to tuts-tab.

CSS related to the tuts-tab can be written inside the <style></style> tags. Remember, all styles declared here will be scoped to the tuts-tab web component. Scoped CSS is a useful feature of Shadow DOM. And, it has the following properties:

If you want to select the custom element inside the shadow DOM, you can make use of the :host pseudo-class. When the :host pseudoclass is used in a normal DOM structure, it would not have any impact. But, inside a shadow DOM, it makes a very big difference. You will find the following :host style in the tuts-tab component. It decides the display, and the font style. This is just a simple example to show how you can incorporate :host in your shadow DOM.

One catch with :host is its specificity. If the parent page has a :host, it will be of higher specificity. All styles inside the parent style would win. This is a way of overriding styles inside the custom element, from outside. 

As your CSS becomes simpler, the overall efficiency of the shadow DOM improves. 

All the styles defined below are local to the shadow root. 

Likewise, you have the freedom to introduce stylesheets within the shadow DOM. When you link stylesheets inside the shadow DOM, they will be scoped within the shadow tree. Here is a simple example to help you understand this concept.

Next, we can define the HTML elements of our tuts-tab.  

In a simple tab structure, there should be titles that can be clicked and a panel that reflects the contents of the selected title. This clearly mean, our custom element should have a div with titles, and a div for the panel. The HTML components will be defined as below:

Inside the panels div, you will come across an interesting tag called <slot>. Our next step is to learn more about slots.

Slot plays a crucial role in the Shadow DOM API. A slot acts as a placeholder inside custom components. These components can be filled by your own markup. There are three different types of slot declaration:

In our tuts-tabs, we have one named slot for the tab titles, and another slot for the panel. The named slot creates holes that you can reference by name.

Now, it is time to populate the slots. In our previous tutorial, we learnt about four different methods for defining custom elements. The tuts-tabs uses two of those methods for building the tab: connectedCallback and disconnectedCallback.

In connectedCallback we will populate the slot defined in step 6. Our connectedCallback will be defined as below. We make use of querySelector to identify the tabsSlot and panelsSlot. Of course, this is not the only way of identifying slots in your HTML. 

Once the slots are identified, you need to assign nodes to it. In tuts-tab, we use the following tabsSlot.assignedNodes to identify the number of tabs. 

Also, the connectedCallback is where we would register all the event listeners. Whenever the user clicks on a tab title, the content of the panel needs to change. Event listeners for achieving this can be registered in the connectedCallback function.

We are not going to drill deep into the logic, on how to implement tabs and its functionality. However, remember that the following methods are implemented in our custom tuts-tab component for switching between tabs:

Moving on, don’t forget to define the disconnectedCallback. This is a lifecycle method in custom elements. When the custom element is destroyed from the view, this callback gets triggered. This is one of the best places to remove action listeners, and reset controls in your application. However, the callback is scoped to the custom element. In our case, it would be tuts-tab.

The final step is to use tuts-tab in our HTML. We can insert tuts-tab, quite easily in the HTML markup. Here is a simple example to demonstrate its usage.

There we go! We have come to the end of an important tutorial where we create and use a custom element. The process is simple, and it proves to be extremely useful while developing web pages. We hope you try creating custom elements, and share your experiences with us.