How to create a web button unaffected by external CSS styles
3 min read

How to create a web button unaffected by external CSS styles

How to create a web button unaffected by external CSS styles

The task seemed simple: create a small, dynamic widget and generate the inline HTML, CSS and JavaScript code that would build it. This way, anyone could copy and paste the code on their site without linking to an unpredictable third-party .js file.

Everything went fine until one of the sites that was using this code had some CSS styles that were affecting the code generated by the widget. A button element, in particular.

In retrospect, it would have been smarter to fit the button with inline !important styles, since you cannot override inline !important styles. However, it is very cumbersome to inline in the code all the possible CSS styles in order to make sure that absolutely nothing from the client site will overwrite your elements. There has to be an even smarter way!

The short answer is: "use the Shadow DOM". The longer answer will now follow.

Let's start by giving you an example of the sort of code I'm talking about.

<div id="unique_id"></div>
<script>
(function (wrapper_id) {
    var wrapper = document.getElementById(wrapper_id);

    var button = document.createElement('button');
    button.onclick = function () {
        alert('I was clicked!');
    }

    wrapper.appendChild(button);
})('unique_id');
</script>

This code should create a regular looking button that displays an alert when clicked. However, it's actually big and red! That's because the CSS style in that page are affecting it.

Screenshot of a dynamically generated button affected by external CSS styles

If we were to rewrite the code using shadow DOM, the new button will reside outside the regular DOM and any external styles will not be able to reach it.

(function (wrapper_id) {
    
    var wrapper = document.getElementById(wrapper_id);
    
    // first, we attach a shadow root
    var shadow_wrapper = wrapper.attachShadow({
        mode: 'open'
    });

    var button = document.createElement('button');
    button.innerText = 'Dynamically generated regular button';
    button.onclick = function () {
        alert('I was clicked!');
    }

    shadow_wrapper.appendChild(button);

})('shadow-button-wrapper');

Basically we attach a shadow root to the target node, where our button or any other internal HTML code will reside.

// first, we attach a shadow root
var shadow_wrapper = wrapper.attachShadow({
    mode: 'open'
});

The mode: 'open' part means the user can see the internal structure when he inspects the page. With mode: 'closed' it would have been unaccessible from the outside, like the native <video> element, for example.

By using the Shadow DOM, our button's styles will now be pristine. You can see in the screenshot below just how not-red it is.

Screenshot of a dynamically generated button NOT affected by external CSS styles

Of course, it will probably his own CSS styles defined inline in the code but the point is outside CSS will not interfere.

As a bonus, you could also use custom elements in combination with shadow DOM for some really neat results.

For example, insted of the weird looking <div id="unique_id"></div> code, how about using <widget-button>Button text<widget-button>? By using this technique, you can encapsulate the inner structure inside the JavaScript code.

// Create a class for the element
class WidgetButton extends HTMLElement {
    constructor() {
        // Always call super first in constructor
        super();

        // Create a shadow root
        const shadow = this.attachShadow({
            mode: 'open'
        });

        // Create a button node and the existing content to it
        const button = document.createElement('button');
        button.textContent = this.innerHTML;
        
        button.onclick = function () {
            alert('I was clicked!');
        }

        // Append it to the shadow root
        shadow.appendChild(button);
    }
}

// Define the new element
customElements.define('widget-button', WidgetButton, {
});

})();

Isn't that both cool and useful?

P.S. You can see the code in action here: Shadow DOM Button.