Style Blocker: How To Prevent CSS Cascade With Shadow DOM
23 Feb 2022
Table of contents
I was recently working on my responsive columns layout system when I encountered a problem.
I wanted to include a demo layout within an article, but I didn't want any of my normal website styles to affect the layout.
Essentially, I wanted to isolate my layout so it was completely unaware of the surrounding page CSS. This meant everything... font family, link colors, heading sizes, EVERYTHING!
How was I going to do that?
My first thought was to use an iframe, but that seemed too messy.
An iframe requires a separate page for the layout which is not good for SEO:
- Google won't index iframe content as part of the parent page.
- Google would likely index the layout as an additional out-of-context page.
I wanted to avoid both of these issues.
An iframe was not the answer.
So I did a bit of research and that lead me to the dark side... the shadow DOM.
I've looked at the shadow DOM before for other things and it always felt a bit too complicated for my needs, but for blocking styles... it seemed PERFECT!
After a bit of experimentation, I came up with the following solution.
Style-Blocker: A Custom Element To Isolate CSS
The first thing I did was to create a custom HTML element <style-blocker>
that I could use as an isolation barrier to block CSS.
I then placed my layout inside the <style-blocker>
element like this.
<style-blocker>
<!-- layout html here -->
</style-blocker>
At this point, nothing has changed. The surrounding page styles still leak into my layout. This is because even though my layout is inside the <style-blocker>
element, it's not inside its shadow DOM.
To fix this, we need to complete the following steps with javascript:
- Register the
<style-blocker>
custom element. - Create a shadow root on
<style-blocker>
. - Move all descendent elements of
<style-blocker>
into its shadow root.
I included a script element on the page below my layout, and wrote the following javascript to complete the above steps:
<script>
class StyleBlocker extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
while (this.childNodes.length > 0) {
this.shadowRoot.appendChild(this.childNodes[0]);
}
}
}
window.customElements.define('style-blocker', StyleBlocker);
</script>
Note: If you include whitespace between the opening <style-blocker>
element and the first element inside, or likewise, between the last element inside and the closing </style-blocker>
then the script will copy this whitespace across into the shadow DOM too. To prevent this, just remove leading and trailing whitespace.
Success!
My layout is now completely isolated from the surrounding page styles! =)
The best thing about this approach is it's SEO-friendly.
Google will index the complete page including the layout because everything is included in the original HTML source of the page.
Even if Google decides to execute javascript on the page, it will still know about the layout inside the shadow DOM and so it should still be indexed.
I was very happy with this solution.
However.
I had another issue.
My layout requires some styles for it to work, so how do I pass the specific styles I need into the shadow DOM?
How To Pass Styles Into A Shadow DOM
To add styles to a shadow DOM, insert a link element that points to an external stylesheet file, here are the steps:
- Create a
<link>
element. - Set the
href
attribute to the CSS filename. - Set the
rel
attribute to 'stylesheet' so the browser knows it's for CSS rules. - Append the
<link>
element to the shadow DOM.
Rather than hardcode the stylesheet filename in my javascript, I decided to add a css-files
attribute to my <style-blocker>
element so I can easily pass in optional CSS files.
To make things super-flexible I allow for a comma-separated list of files just in case I need to pass in more than one.
Here's the HTML showing the extra attribute on style-blocker:
<style-blocker css-files="/css/layout.css">
<!-- layout html here -->
</style-blocker>
Here's the completed javascript including CSS injection:
<script>
class StyleBlocker extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
while (this.childNodes.length > 0) {
this.shadowRoot.appendChild(this.childNodes[0]);
}
const cssFiles = this.getAttribute('css-files');
if (cssFiles) {
const arrCssFiles = cssFiles.split(',');
for(let i = 0; i < arrCssFiles.length; i++){
let link = document.createElement('link');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('href', arrCssFiles[i]);
this.shadowRoot.appendChild(link);
}
}
}
}
window.customElements.define('style-blocker', StyleBlocker);
</script>
Now I can easily isolate parts of my page from the surrounding page styles and inject alternate stylesheets.
A perfect solution that's easy to do!
Shadow DOM vs Iframe
There are some important differences between shadow DOMs and iframes that I want to point out.
Iframes:
- Completely separate indexable page.
- Content not indexed as part of the parent page.
- Iframe width is equal to the viewport width, this means any breakpoints you have set up will be relative to the width of the iframe, not the parent page.
Shadow DOM:
- Part of the main page.
- Can remain indexable by Google when included in the original page source.
- Breakpoints are relative to the width of the parent page, not the shadow DOM element.
Summary
The shadow DOM makes it easy to block surrounding page styles from cascading into specific elements. Injecting styles into a shadow DOM is easy too. The methods I've described above are also SEO friendly so there is no danger of Google not indexing important parts of your page.
Want a live demo of style-blocker? Check out my article: Holy grail 3-column responsive layout, the demo layout near the top of the page is in a shadow DOM.
- Use your browser inspect tool to navigate around the shadow root.
- Turn off javascript and the layout will still be visible, just a non-isolated version.
Overall, I'm impressed with what the shadow DOM can do so I will be experimenting with it further.
“I've been developing websites professionally for over two decades and running this site since 1997! During this time I've found many amazing tools and services that I cannot live without.”
— Matthew James Taylor
I highly Recommend:
Ezoic — Best ad network for publishers
Earn more than double the revenue of Google Adsense. It's easy to set up and there's no minimum traffic requirements.
SiteGround — Best Website Hosting
Professional Wordpress hosting with 24/7 customer support that is the best in the industry, hands down!
Surfer SEO — Best Content Optimization Tool
Use the power of AI to optimise your website content and increase article rankings on Google.
Canva — Best Graphic Design Software
Create professional graphics and social media imagery with an intuitive online interface.
See more of my recommended dev tools.