During our last Thursday Ionic Zürich Meetup, after having showcased my last pet project DeckDeckGo, the new tool to create presentation using HTML and Web Components, I found myself explaining how to style a Shadow DOM and what are Slots with the help of a really simple unprepared example. As it was really fun and, I hope, easy to follow, I thought it would maybe be not that a bad idea to share the experience with a new article 😋

But, actually, the following won’t really be an article but rather a step by step practical introduction. Therefore I encourage you to open a browser and a terminal in order to try by yourself each steps displayed here under 🕹👇

Introduction to Shadow DOM

I will not go to deep in details but let’s just say that Shadow DOM are elements, used in our web page, which have their own little kingdom. Let’s even understand these components as castles with fortifications which only allow you to interact or discuss with them under certain rules or channels they would provide or not.

Let’s get started using Stencil

For the purpose of this tutorial we are going to use Stencil. First of all because, per default, components created with Stencil are shadowed and secondly because Stencil is just fun and super handy 😉

In a terminal, run the following commands and follow the following steps to create a new project:

Run “npm init stencil” to start a new project

Select “component” to create a new Web Component

Enter a fun project’s name and confirm

Switch to the newly created folder and start “npm run start” to serve locally the newly created project

If everything went well your browser should now be opened and a web page containing “Hello World! I’m Stencil ‘Don’t call me a framework’ JS” should be rendered. If you follow the steps in parallel, I would encourage you to open your browser’s debugger too.

Default component created by Stencil

In the next steps of this tutorial we will play with the three following files Stencil just created for us:

./src/index.html the web page itself ./src/components/my-component/my-component.css the CSS code of the Web Component, respectively the element we are going to play with ./src/components/my-component/my-component.tsx the component itself, most precisely we will later edit the method render() which, I guess you understood, renders the component and which looks like per default like the following:

render() {

return <div>Hello, World! I'm {this.format()}</div>;

}

You probably noticed too that this last method renders a <div/> which actually doesn’t appear in the previous screenshot. The reason behind this is the fact that our element is shadowed, therefore its content, the <div/> , is part of its little word, of its little castle. But there is a way to discover it in your browser debugger, just expand the #shadow-root pseudo-element and you will find it.

Expand pseudo-element #shadow-root to discover the <div/> of the Web Component

Let’s try to style the component

Now that we have a web page which renders a shadowed (= Shadow DOM, see above screenshot #shadow-root ) Web Component, we could try to style that element.

Styling a Shadow Dom element from outside has no effect

Or we could also try to style the content, the <div/> , of the component using a CSS selector.

Styling a Shadow Dom element from outside has really no effect

You probably noticed, this has no effect. The reason behind is that Shadow Dom have their own little kingdom and cannot be styled from outside, except if they would provide some options to do so.

Let’s then try to add a styling option

To add a styling option to the component, we could use CSS4 variables. Furthermore than giving the ability to be modified at runtime, these new type of variables give us the ability to define styles' options which could be defined from outside of a Shadow Dom.

Concretely let’s add the following style to the my-component.css file.

div {

background: var(--my-background, yellow);

}

This above style could be interpreted as the following: all <div/> elements in the shadow of our Web Component have a default background yellow as long as the CSS4 variable --my-background is not specified.

If you go back to our browser, you should now find a yellowish rendered background .

Per default, the shadow <div/> has a yellow background

Let’s then try to style it again but this time using the CSS4 variable we just defined respectively by assigning a value to that variable instead of defining a background property.

Hooray

Hooray we were able to style our shadowed elements from outside 🎉

Let’s try now to add (sub)content

There might be use cases where we would like to inject some content inside a shadowed element. Per default, as for styling, it isn’t possible from outside as long as the component, the little castle, don’t allow this option.

Let’s then first try to inject a <p/> in your current Web Component. To do that we will modify the ./src/index.html file like the following:

<my-component first="Stencil" last="'Don't call me a framework' JS">

<p>HELLO</p>

</my-component>

Note: ignore first and last attributes in the scope of this article. These are attributes which comes per default as example with the starter kit of Stencil. We will not use these in this tutorial.

Injecting an element has per default no effect

As expected, injecting an element without any other indication doesn’t work. Fortunately, as for styling, a shadow element could give the ability to inject elements from outside using <slot/> markups.

Let’s then try to add a <slot/> to our component by modifying the my-component.tsx file like the following:

render() {

return <div>Hello, World! I'm {this.format()}

<slot></slot>

</div>;

}

If we go back to our browser, our element should now be rendered 🎊

Slots allow the injection of elements from outside

A bit more about Slots

<slot/> could also be defined with names which would allow us to add multiple of them inside the same component and even to order them.

For example, we could try to add two slots in our component, one called start and a second one called end . Let’s then modify our my-component.tsx file like the following:

render() {

return <div>Hello, World! I'm {this.format()}

<slot name="start"></slot>

<slot name="end"></slot>

</div>;

}

Cool, our component contains now two slots. If we go back to our browser you may notice that our previous element isn’t rendered anymore 🤔

Element not rendered in slot anymore?

As we didn’t reflect the naming of our slots (“start” and “end”) or as we didn’t let a slot without name attribute in our component, the element can’t be rendered. But easy peasy, let’s just specify the attribute and everything should go back to normal. To do so, let’s modify the ./src/index.html file like the following:

<my-component first="Stencil" last="'Don't call me a framework' JS">

<p slot="end">HELLO</p>

</my-component>

We provided a slot name, element is rendered

As you could notice, our element is back 😃 Let’s try now to add another element targeting the other slot. To do so, let’s modify the ./src/index.html file like the following:

<my-component first="Stencil" last="'Don't call me a framework' JS">

<p slot="end">HELLO</p>

<p slot="start">WORLD</p>

</my-component>

Note that we explicitly entered start after end . If we go back to our browser, this should be rendered like the following:

Two slots rendered

Our paragraphes aren’t displayed in the same order as we typed them, interesting right? The reason behind is simply that the definition of the shadowed elements is decisive, which allows the creator of the component to ensure its order.

Final bit of fun, styling slots

As we have now slots, respectively paragraphes, let’s try to style these. As previously, let’s try first to style them from outside. To do that let’s add a style in the <head/> of the ./src/index.html file like the following:

<style rel="stylesheet">

my-component p {

background: green;

}

</style>

Let’s go back to our browser now:

Slots could be styled from outside

Wait what these paragraphes are green? Yes that’s right, these are green 😉 The reason behind is that these elements are not shadowed in the component but injected with the help of slots. Summarized: <slot/> could be style from outside 😁

Let’s now try to style the slots from the component itself, from “inside” this time. To do so, let’s add the following style to the my-component.css file:

p {

background: blue !important;

}

And let’s go back to our browser:

Styling a slotted element from the shadowed component doesn’t work?

Wait what these paragraphes are green? We used the !important marker, why then? Yes as previously mentioned, the elements we inject are not properly shadowed but, there is a way to style them 😉 To do so we could use the CSS selector ::slotted which should allow us to target them. Let’s then try to modify the style of the my-component.css file like the following:

::slotted(p) {

background: blue !important;

}

And let’s go back to our browser again:

::slotted selector allow the style slotted elements

Et voilà, using ::slotted we were able to style elements we injected in slots 🎉

Cherry on the cake 🍒🎂

I hope this step by step introduction to styling a Shadow Dom and Slots was a bit helpful. If it was, then the cherry on the cake is probably that you have now basically understood the only real big change, in my point of view, introduced by Ionic v4. Of course this version introduces other important improvements and changes but for me, styling was the biggest challenge and I hope that this small introduction gave you a better understanding of these particular subjects and will also encourage you to take the plunge and jump into Ionic v4 or even to Stencil because these are just awesome 🤘

To infinity and beyond 🚀

David