I have a custom element (without shadow DOM) that I'd like to be able to use anywhere, even inside another custom element that might use shadow DOM. However, I'm not sure how to get the styles working in both places.
For example, lets say I create a simple fancy-button
element:
class fancyButton extends HTMLElement {
constructor() {
super();
this.innerHTML = `
<style>
fancy-button button {
padding: 10px 15px;
background: rgb(62,118,194);
color: white;
border: none;
border-radius: 4px
}
</style>
<button>Click Me</button>`;
}
}
customElements.define('fancy-button', fancyButton);
<fancy-button></fancy-button>
Inside a shadow DOM element, the inserted style tag will allow the fancy-button
styles to work. However, if this component gets used outside of a shadow DOM element, the style tag will be duplicated every time the element is used.
If instead I add the style tag as part of the html import file, then the styles only work outside of the shadow DOM but at least they are only declared once.
<!-- fancy-button.html -->
<style>
fancy-button button {
padding: 10px 15px;
background: rgb(62,118,194);
color: white;
border: none;
border-radius: 4px
}
</style>
<script>
class fancyButton extends HTMLElement {
constructor() {
super();
this.innerHTML = `<button>Click Me</button>`;
}
}
customElements.define('fancy-button', fancyButton);
</script>
What's the best way to add custom element styles that handles both being used inside and outside the shadow DOM?
So I was able to find a solution thanks to Supersharp suggestions about checking if we're in the shadow DOM.
First you add the styles as part of the import file so that the styles apply outside of the shadow DOM by default. Then when element is added to the DOM, we check getRootNode()
to see if it's been added to a ShadowRoot
node. If it has, and the styles haven't already been injected into the root, then we can inject the styles manually.
var div = document.createElement('div');
var shadow = div.attachShadow({mode: 'open'});
shadow.innerHTML = '<fancy-button></fancy-button>';
document.body.appendChild(div);
<style data-fs-dialog>
fancy-button button {
padding: 10px 15px;
background: rgb(62,118,194);
color: white;
border: none;
border-radius: 4px
}
</style>
<script>
class fancyButton extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.innerHTML = `<button>Click Me</button>`;
var root = this.getRootNode();
// In polyfilled browsers there is no shadow DOM so global styles still style
// the "fake" shadow DOM. We need to test for truly native support so we know
// when to inject styles into the shadow dom. The best way I've found to do that
// is to test the toString output of a shadowroot since `instanceof ShadowRoot`
// returns true when it's just a document-fragment in polyfilled browsers
if (root.toString() === '[object ShadowRoot]' && !root.querySelector('style[data-fs-dialog]')) {
var styles = document.querySelector('style[data-fs-dialog]').cloneNode(true);
root.appendChild(styles);
}
}
}
customElements.define('fancy-button', fancyButton);
</script>
<fancy-button></fancy-button>
When all browsers support <link rel=stylesheet>
in the shadow DOM, then the inline script can turn into an external stylesheet as robdodson suggested, and the code is a bit cleaner.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With