A practical introduction to styling a Shadow DOM and Slots

David Dal Busco
Nov 12, 2018 · 8 min read

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

Let’s get started using Stencil

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:

  1. ./src/index.html the web page itself
  2. ./src/components/my-component/my-component.css the CSS code of the Web Component, respectively the element we are going to play with
  3. ./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

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

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

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

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

<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 🍒🎂

To infinity and beyond 🚀

David

Stencil Tricks

A collection of community-written articles on how to do awesome things in Stencil JS

David Dal Busco

Written by

Freelancer by day | Creator of DeckDeckGo by night | Organizer of the Ionic Zürich Meetup

Stencil Tricks

A collection of community-written articles on how to do awesome things in Stencil JS

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade