Superman blocking styles

Web design

Style Blocker: How To Prevent CSS Cascade With Shadow DOM

Matthew James Taylor23 February 2022

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:

  1. Register the <style-blocker> custom element.
  2. Create a shadow root on <style-blocker>.
  3. 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:

  1. Create a <link> element.
  2. Set the href attribute to the CSS filename.
  3. Set the rel attribute to 'stylesheet' so the browser knows it's for CSS rules.
  4. 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.

Next up:
How to add CSS to HTML
Responsive Font Size

Web Design Articles

Looking into an empty div

Empty HTML Tags (21 Weird Things You Need To Know!)

Web design
Columns all the same height

Equal-Height Columns (CSS Grid, Flexbox, Floated Containers, & Table Methods)

Web design
How to add CSS to HTML

How to add CSS to HTML (With Link, Embed, Import, and Inline styles)

Web design
A delicious soup made from custom elements

Replace Divs With Custom Elements For Superior Markup

Web design
Padding bewteen desktop, tablet, and mobile

Responsive Padding, Margin & Gutters With CSS Calc

Web design
Responsive text size

Responsive Font Size (Optimal Text at Every Breakpoint)

Web design
Responsive Columns Layout System

Responsive Columns: Build Amazing Layouts With Custom HTML Tags

Web design
Responsive house plan

Responsive House Plan (Web Design Meets Architecture!)

Architecture
Boggle dice shaker

Boggle Dice Shaker (Built With Javascript)

Web design
Footer at the bottom of the page

Get Down! How to Keep Footers at the Bottom of the Page

Web design
Is CSS margin top or bottom better?

CSS: Margin Top vs Bottom (A Trick You Should Know)

Web design
Beautiful centered menus with CSS

CSS: Horizontally Centred Menus (With Optional Dropdowns)

Web design
Ads that can change size to fit any screen size

Responsive Banner Ads with HTML5 and CSS3

Web design