Mandala   / / / / / / / / / / / /   / / / / / / / / / / / /   / / / / / / / / / / / /   / / / / / / / / / / / /   / / / / / / / / / / / /   / / / / / / / / / / / /   / / / / / / / / / / / /  + + + + + +  + + + + + +  + + + + + +  + + + + + +  + + + + + +  + + + + + +  + + + + + +{ { {} } } }  { { {} } } }  { { {} } } }  { { {} } } }  { { {} } } }  { { {} } } }      ../../    ../../    ../../    ../../    ../../    ../../    ../../ <></><></><></> <> </><></><></> <> </><></><></> <> </><></><></> <> </><></><></> <> </>

Encapsulating CSS with JavaScript and Shadow DOM

Article Parts

Shadow DOM

The Shadow DOM API can be used like an iFrame to isolate CSS and JavaScript. In this article, we will provide an example of encapsulating CSS (isolating CSS) using JavaScript and Shadow DOM. An important piece of information relating to the Shadow DOM is that it is still rendered within the document, but it is actually a separate hidden dom that is attached to an element. With Shadow DOM, styles don’t leak in or out, except with a few exceptions that can be inherited. The styles that can be inherited to Shadow DOMs come from the styles set on the HTML and BODY elements. So for example, if font-family is set on the HTML element, then the Shadow DOM will inherit the style. These styles can be overwritten. So to summarize, Shadow DOM can be used as self-contained web components. Web components are a mix of shadow DOM, custom elements, and HTML templates which are intitated by JavaScript.

Browser Support

Shadow DOM Browser Support

As you can see in the provided image, currently 95% of all browsers support the Shadow DOM API.

Encapsulating CSS with a Shadow DOM

Encapsulated CSS and HTML Elements using JavaScript

Here above is an example of encapsulating CSS. The CSS in this code is loaded into a Shadow DOM using JavaScript. The only styles applied are the styles loaded into the innerHTML of the Shadow DOM attached to the custom modal element. There is one exception, this is the font-family CSS style applied to the HTML element in the main document inline CSS style block.

Attaching a Shadow DOM to an Element

<modal-box></modal-box><button id="open-modal">Open Modal</button>
html { font-family: Roboto,arial,sans-serif;}#open-modal { color: #707075; margin-top: 40px; transition: color 300ms; background: transparent; border: 0; color: #f2f2f2; font-size: 20px; height: 30px; line-height: 30px; outline: none !important; width: 100%;}#open-modal { margin: 5% auto 0; width: 200px; height: 50px; background-color: #fff; border: 2px solid #676767; animation-name: float; animation-timing-function: ease-in-out; animation-duration: 2s; animation-direction: alternate; animation-iteration-count: infinite; text-align: center; line-height: 50px; font-size: 16px; color: #f4f5f7; background: #4d3fa3; border: 1px solid #4d3fa3; color: #f4f5f7; font-weight: 500; border-radius: 3px; display: block; cursor: pointer; padding: 0 16px; transition: all 250ms; box-shadow: 0 2px 4px rgb(0 0 0 / 50%);}
'use strict';class Modal extends HTMLElement { static get observedAttributes() { return ['open']; } constructor() { super(); this.attachShadow({ mode: 'open' }); this.close = this.close.bind(this); } attributeChangedCallback(attrName, oldValue, newValue) { if (oldValue !== newValue) { this[attrName] = this.hasAttribute(attrName); } } connectedCallback() { const { shadowRoot } = this; shadowRoot.innerHTML = `<style> svg { width: 24px; fill: #fff; pointer-events: none; vertical-align: top; } .close { position: absolute; top: 0; right: 0; margin: 1.2rem; padding: 0.6rem; background: rgba(0,0,0,0.3); border-radius: 50%; } .close:focus {outline:0;} .close:hover { background: rgba(0,0,0,0.6); } .wrapper { opacity: 0; transition: visibility 0s, opacity 0.25s ease-in; } .wrapper:not(.open) { visibility: hidden; } .wrapper.open { align-items: center; display: flex; justify-content: center; height: 100vh; position: fixed; top: 0; left: 0; right: 0; bottom: 0; opacity: 1; visibility: visible; } .overlay { background: rgba(0, 0, 0, 0.8); height: 100%; position: fixed; top: 0; right: 0; bottom: 0; left: 0; width: 100%; } .dialog { background: #ffffff; max-width: 600px; padding: 3rem; position: fixed; box-shadow: 0 12px 15px 0 rgb(0 0 0 / 25%); } button { all: unset; cursor: pointer; font-size: 1.25rem; position: absolute; top: 1rem; right: 1rem; } </style> <div class="wrapper"> <div class="overlay"></div> <div class="dialog" role="dialog" aria-labelledby="title" aria-describedby="content"> <button class="close"> <svg class="" viewBox="0 0 24 24"><path d="M19 6.41l-1.41-1.41-5.59 5.59-5.59-5.59-1.41 1.41 5.59 5.59-5.59 5.59 1.41 1.41 5.59-5.59 5.59 5.59 1.41-1.41-5.59-5.59z"></path><path d="M0 0h24v24h-24z" fill="none"></path></svg> </button> <h1 id="title">Hello World!</h1> <div id="content" class="content"> <p>This is content in the body of our modal. The CSS Style and HTML Elements are isolated from the main document.</p> </div> </div> </div>`; shadowRoot.querySelector('button').addEventListener('click', this.close); shadowRoot.querySelector('.overlay').addEventListener('click', this.close); this.open = this.open; } disconnectedCallback() { this.shadowRoot.querySelector('button').removeEventListener('click', this.close); this.shadowRoot.querySelector('.overlay').removeEventListener('click', this.close); } get open() { return this.hasAttribute('open'); } set open(isOpen) { const { shadowRoot } = this; shadowRoot.querySelector('.wrapper').classList.toggle('open', isOpen); shadowRoot.querySelector('.wrapper').setAttribute('aria-hidden', !isOpen); if (isOpen) { this._wasFocused = document.activeElement; this.setAttribute('open', ''); document.addEventListener('keydown', this._watchEscape); this.focus(); shadowRoot.querySelector('button').focus(); } else { this._wasFocused && this._wasFocused.focus && this._wasFocused.focus(); this.removeAttribute('open'); document.removeEventListener('keydown', this._watchEscape); this.close(); } } close() { if (this.open !== false) { this.open = false; } const closeEvent = new CustomEvent('dialog-closed'); this.dispatchEvent(closeEvent); } _watchEscape(event) { if (event.key === 'Escape') { this.close(); } }}customElements.define('modal-box', Modal);const button = document.getElementById('open-modal');button.addEventListener('click', () => { document.querySelector('modal-box').open = true;})