If you are a web developer, chances are that you have used Java Script libraries. If so, you owe it to the unknown heroes who ensure that these libraries work.
One unanimous problem with these brave fighters is encapsulation. The cornerstone on which the OOP’S (Object Oriented Programing) foundation sits and holds most of the modern software development. How do you define that boundary? Now there’s a code that you wrote and a code that will consume it.
Today’s Modern Websites often contain objects and widgets from various sources, bashed on to single webpage. Consider this for example, a single web page can contain features like a YouTube video embedded, a Facebook ‘Like’ button or a Twitter ‘Tweet’ button. Now the matter of the fact is that code from so many sources can coexist on one webpage is nothing short of Genius. For example, adding a “tweet” button to a page requires the use of a clunky <iframe> tag.
“What do you mean I must put each of my custom buttons in a separate iframe? What kind of insane are you?”
So we obviously need something better, don’t’ we? As it turns out, most browsers have a secret technique to hide their way of interpretation. This technique is called the shadow DOM.
“The Name’s DOM, Shadow DOM!”
Shadow DOM provides encapsulation for the JavaScript, CSS, and templating in a Web Component. Shadow DOM makes it so these things remain separate from the DOM of the main document. Shadow DOM can be used on its own, outside of a web component. It takes care of the DOM tree encapsulation problem.
It is one of the four Web Component standards: Shadow DOM, HTML Template, HTML Imports and Custom elements. These four parts of Web Components are designed to work together.
Web Components are a set of top notch standards that make it possible to build widgets which can be reused reliably and which won’t break webpages if the next update of the component changes internal way-of-implementation details.
Shadow DOM is designed as a tool for building reusable user interface widgets. Therefore, it brings forward a solution to the following problems:
Isolated DOM: an element of DOM is self-contained (e.g. document.querySelector() won’t return nodes in the component’s shadow DOM).
Scoped CSS: CSS declared within a shadow DOM is scoped to it. <Styles> rules don’t let it out & webpage <styles> don’t bleed in.
Simplifies CSS – scoped DOM lets you use simple CSS selectors, more generic class/id names, and not worry about naming conflicts.
Productivity tool – think of apps in chunks of DOM rather than one large (global) page.
Now Let’s Enter Into World of Shadows!
Background on DOM
HTML fuels the web because it’s easy to learn and work with. Define a few tags and you have got yourselves a webpage in seconds. However, by itself HTML cannot do everything. Although for humans it’s easy to understand a text-based language, but you can’t communicate with machines in this language. That’s where Document Object Model, or DOM comes into the picture.
Do you know what happens when a browser is provided with a web page to display? Well pretty interesting stuff. It reads and transforms the HTML code into a live webpage. Now to understand the webpage’s structure, the browser transforms HTML into a data model. The browser then preserves the HTML’s hierarchy by creating a tree out of these nodes: the Document Object Model (DOM). In Contrast to the static HTML we write down, the browser-hatched nodes contain methods, properties and can be manipulated by programs. That’s why we can create DOM elements directly using JavaScript:
const header = document.createElement('header');
const h1 = document.createElement('h1');
h1.textContent = 'Hello world!';
header.appendChild(h1);
document.body.appendChild(header);
Produces the equivalent of the following HTML markup:
<body>
<header>
<h1>Hello DOM</h1>
</header>
</body>
What is Shadow DOM?
Shadow DOM is almost like a normal DOM but with two differences:
1) The way it is created
2) Behavior in context to the page.
Usually, you would create DOM nodes & supplement them as children of other element. But shadow DOM works differently, for say, you create a scoped DOM tree that’s attached to the element, but keeping it apart from its actual children. This scoped subtree is a shadow tree. The element it’s attached to is, its shadow host. You add anything in the shadow, it becomes a local to the hosting element. This is how shadow DOM achieves CSS style scoping. Pretty Cool huh!
Structure of Shadow DOM
Shadow host is an element that has a shadow root associated with it. The shadow root can be treated as a DOM element so you can append arbitrary nodes to it.
Creating Shadow DOM!
Shadow DOM should always be attached to an existing element. The element can be either a predefined element an element created in the DOM by scripting OR an HTML file, or. It can be a predefined element like <p> or <h1>, or a custom element like <new-element>. You import shadow DOM via Element.createShadowRoot() like in this example:
<html>
<head></head>
<body>
<p id="hostElement"></p>
<script>
var shadow = document.querySelector('#hostElement').createShadowRoot();
</script>
</body>
</html>
Styling Shadow DOM
Shadow boundary is one of the core features of Shadow DOM.. It has a lot of practical implications, but one of the best is that it provides style encapsulation for free.
Result:
Moral of the story?
Thanks to Shadow DOM, We have style encapsulation from the outside world.
Composition and slots
Composition is tricky and one of the least understood features of shadow DOM, but arguably the most important one.
In web development, composition is how we build apps. Different building blocks (<div>s, <header>s, <form>s, <input>s) come together to form one big app. Some of the tags even work with each other. Composition is why traditional elements like <details>, <select>, <video> and <form> are so flexible. Each of those tags takes HTML as children and does something special with them. For example, <select> knows how to provide <option> and <optgroup> into dropdown widgets. The <details> element renders <summary> as an expandable arrow, <video> knows how to deal with certain children: <source> elements don’t get rendered, but they do affect the video’s behavior.
Shadow DOM composition introduces a bunch of new fundamentals in web development.
Light DOM
The markup a user of your component writes. This DOM lives outside the component’s shadow DOM. It is the element’s actual children.
<button is="better-button">
<!-- the image and span are better-button's light DOM -->
<img src="gear.svg" slot="icon">
<span>Settings</span>
</button>
Composed DOM
The result of the browser distributing the user’s light DOM into your shadow DOM, rendering the final product. The composed tree is what you ultimately see in the DevTools and what’s rendered on the page.
<button is="better-button">
#shadow-root
<style>...</style>
<slot name="icon">
<img src="gear.svg" slot="icon">
</slot>
<slot>
<span>Settings</span>
</slot>
</button>
Styling a component from the outside
There are a couple of ways to style a component from the outside.
The easiest way being to use the tag name as a selector:
fancy-tabs {
width: 500px;
color: red; /* Note: inheritable CSS properties pierce the shadow DOM boundary. */
}
fancy-tabs:hover {
box-shadow: 0 3px 3px #ccc;
}
Outside styles always have an edge over styles defined in shadow DOM. For example, if the user writes the selector fancy-tabs { width: 300px; }, it will trump the component’s rule: :host { width: 450px;}.
Style the component itself will only get you so far. You will need CSS custom properties if you want to style the internals of a component.
Using CSS custom properties, users can still change internal styles if the component’s author provides styling hooks. In theory, the idea is similar to <slot>. You create “style placeholders” for users to override.
Example – <fancy-tabs> allows users to override the background color:
<!-- main page -->
<style>
fancy-tabs {
margin-bottom: 32px;
--fancy-tabs-bg: black;
}
</style>
<fancy-tabs background>...</fancy-tabs>
Inside its shadow DOM:
:host([background]) {
background: var(--fancy-tabs-bg, #9E9E9E);
border-radius: 10px;
padding: 10px;
}
In this case, the component will use black as the background value since the user provided it. Otherwise, it would default to #9E9E9E.
We here at ClixLogix, design & develop what others don’t dare to. With over 5+ Years of experience in delivering custom web applications, we approach each project with a high sense of work. Recently a project regarding development of a custom web application came our way which involved the use of Shadow DOM and with our reliable & certified web development professionals at work, we got a chance to show off our mad UI skills.
Not giving much away it’s a web application in the niche of team & project management which is quite similar to Slack. Here’s how we incorporated Shadow DOM into our project, keeping it as simple as I can:
1. If you are here, reading this blog post, chances are you are using a messaging app. The web application we are building is something similar and it incorporates a ‘Search’ feature to help users find stuff at click of a button. How will we use shadow DOM here? Well we’ll be defining the ‘Search’ feature at one instance and with the help of shadow DOM, will be using this search toggle as a reusable widget all around the place.
2. There are lots of alternatives for styling web components, but keeping in mind the efficiency and productivity, we went with the following approach. A component that uses shadow DOM can be styled by the main page, define its own styles in the form of CSS custom properties to override default. Went over your head? In Laymen’s term we can say the overall basic design and the layout from the chat window to notifications to the channel section can be defined once in HTML using shadow DOM and called again and again as a reusable feature at places.
3. Shadow DOM isn’t just usable to give a shout out to the predefined custom elements. Another great functionality it adds to your application is security. Encapsulating your method and its function using shadow DOM makes sure that your <code> is secure from the likes of poachers. Even if somebody is able to inspect the code, all they will be getting is calling method. Now since we are designing a web application that will be used for communication between the professionals, security becomes a top priority. We used shadow DOM to its full potential to make the application secure.
Conclusion
Now this was just Shadow DOM 101. This goes way beyond this but for someone starting with Shadow DOM, this should provide insights for a kick-off. For the first time ever, we have an API primitive that does proper CSS scoping, DOM scoping, and has true composition. Combined with other web component APIs like custom elements, shadow DOM provides a way to author truly encapsulated components without hacks or using older baggage like <iframe>s.
Don’t take it wrong. Shadow DOM is a complex beast! But it’s worth learning. Spend some time with it. Learn it and ask questions!