To get started run this JS line on your devtools console to check for its feature support
HTMLTemplateElement.prototype.hasOwnProperty('shadowRoot');
On Chrome, if it returns false, you can open this chrome://flags/#enable-experimental-web-platform-features on your Chrome address bar, enable it and restart your browser to try again.
This page uses the polifyll mentioned in the original article where I learned about the feature
Stylesheet affects all elements globally by default but using template shadowroot attribute on a custom element we can scope the styles without JS in a way it won't inherit styles from its parent elements and its internal styles won't affect elements outside the shadowroot boundaries avoiding style collisions with third-parties
If we want to completely ignore global styles, we start by not using <slot> inside <template> elements
<ce1-host>
<template shadowroot="open">
<form>
<input type="text" placeholder="Fun fact" required />
<button type="button">Click</button>
</form>
</template>
</ce1-host>
Result:
The styles are vanished and you should be looking at the browser default styles
When using slot inside template, be aware slots inherit styles from its parent element's styles. So let's start with an example
<ce1-host>
<template shadowroot="open">
<slot name="form"></slot>
</template>
<form slot="form">
<input type="text" placeholder="Fun fact" required />
<button type="button">Click</button>
</form>
</ce1-host>
The result is as follow
All the styles applied to the form are global styles on this page, this is exactly what we want to have control over. Whenever we use slot the element used with it inherit parent styles
Lets add some <style> in a way that only our scoped element is going to take advantage of the style
<ce1-host>
<template shadowroot="open">
<style>
form {
background: blue;
}
</style>
<form>
<input type="text" placeholder="Fun fact" />
<button type="button">Click</button>
</form>
</template>
</ce1-host>
Result:
The take away from this example, is how general is the selector used to target the form element.
We can define custom properties and document the ones our custom element uses internally, so people can override them on the :host element
<ce1-host>
<template shadowroot="open">
<style>
:host {
--theme-accent-color-100: yellow;
--theme-input-control-padding-block: 0.6rem;
--theme-input-control-padding-inline: 1rem;
--theme-input-margin-inline-end: 1rem;
--theme-form-padding-inline: 1rem;
--theme-form-padding-block: 1rem;
display: flex;
justify-content: center;
}
input, button {
padding-block: var(--theme-input-control-padding-block);
padding-inline: var(--theme-input-control-padding-inline);
}
input {
margin-inline-end: var(--theme-input-margin-inline-end);
}
form {
padding-block: var(--theme-form-padding-block);
padding-inline: var(--theme-form-padding-inline);
background: var(--theme-accent-color-100);
display: flex;
}
</style>
<form>
<input type="text" placeholder="Fun fact" />
<button type="button">Click</button>
</form>
</template>
</ce1-host>
Result:
In this way if the global escope wants to change the custom element scoped CSS, we can do so by targeting the host element and overriding its custom properties
<style>
.pinky-vibe {
--theme-accent-color-100: pink;
--theme-input-control-padding-block: 0.6rem;
--theme-input-control-padding-inline: 1rem;
--theme-input-margin-inline-end: 0.1rem;
--theme-form-padding-inline: 0.1rem;
--theme-form-padding-block: 0.1rem;
}
</style>
Result:
There's also a possibility to instead of using <style> we use a <link rel="stylesheet">, So, rewriting the example above with an external stylesheet it looks like this:
custom-form.css
:host {
--theme-accent-color-100: yellow;
--theme-input-control-padding-block: 0.6rem;
--theme-input-control-padding-inline: 1rem;
--theme-input-margin-inline-end: 1rem;
--theme-form-padding-inline: 1rem;
--theme-form-padding-block: 1rem;
display: flex;
justify-content: center;
}
form {
background: var(--theme-accent-color-100);
display: flex;
}
input,
button {
padding-block: var(--theme-input-control-padding-block);
padding-inline: var(--theme-input-control-padding-inline);
display: block;
}
input {
margin-inline-end: var(--theme-input-margin-inline-end);
}
form {
padding-block: var(--theme-form-padding-block);
padding-inline: var(--theme-form-padding-inline);
}
HTML Markup
<ce1-host class="pinky-vibe">
<template shadowroot="open">
<link rel="stylesheet" href="./custom-form.css" />
<form>
<input type="text" placeholder="Fun fact" />
<button type="button">Click</button>
</form>
</template>
</ce1-host>
<ce1-host class="pinky-vibe second">
<template shadowroot="open">
<link rel="stylesheet" href="./custom-form.css" />
<form>
<input type="text" placeholder="Fun fact" />
<button type="button">Click</button>
</form>
</template>
</ce1-host>
One possibly advatange of using an external stylesheet is that the style within it doesn't get repeated on each ocasion it occurs in our markup. If there's two forms, we need to be careful about the markup but we can almost forget the style
Result:
The main perk of this API is the possibility to scope CSS without any
JavaScript,
because we know there's a precious delay while a JS loads to
customElements.define the markup, and usually appendChild a chunk of CSS
to style the element as well
The less network and less JS runtime envolved, the better it leads to faster perceived performance on load. Cause the HTML and Styles are there in the critical rendering path.
On this page I use declarative shadowroot on <ce-code> on each ocurrence of code highlight to avoid global style collisions, my HTML looks like this:
<ce-code>
<template shadowroot="open">
<link
rel="stylesheet"
href="../../polyfills/highlight/styles/default.css"
/>
<pre><code class="html">
My scaped HTML code
</code></pre>
</template>
</ce-code>
Even though I have multiple mention to the same external stylesheet, it loads once and works as expected for all <ce-code> templates
I presented how the API works in a way I can understand it, now, I list some of my opnions which I think can contribute to the development and discussion of these API on the web platform
The possibility to scope CSS from its global inheritance avoiding collisions is HOT 🔥 but, WHAT IF, we could tell CSS how to rearrange its own inheritance using a property?
If there's a clean separation of concerns
Why would we need HTML or JS, to tell CSS it may ignore inheritance of parent styles?