On this page

Styling

Adding styles to the many parts of a web component can seem tricky. There will be internal elements that you want to style and other parts you want to be left open, to be styled and configurable from the outside. Styling has its limits however and it can seem difficult to understand how things work and how to change different parts.

Leaking into Shadow DOM

You can create a web component with or without a shadow DOM. A shadow DOM acts like a barrier between the inner parts and the outside document. This can be very useful when it comes to stopping outside styles from effecting the internal elements. Let's take a look at an example and see what this means in more detail.

There are two web components being used. The first has a shadow DOM and the other does not. Take a look at a section of code from each one.

// Set inner HTML (into shadow DOM)
this.shadowRoot.innerHTML =
'<div>With Shadow DOM</div><div class="test">Test class text.</div>';
...
// Set inner HTML (without having a shadow DOM)
this.innerHTML =
'<div>Without Shadow DOM</div><div class="test">Test class text.</div>';

Each one is using the <div> tag. In the example we are styling the tag so that all <div> elements are given a larger font and a background color. Each one also has an inner part with the class test linked to it. We are not defining the test class within the web component, so it shouldn't do anything.

<style>
  div {
    font-size: x-large;
    background-color: lightblue;
  }
  .test {
    color: red;
  }
</style>
<p>...</p>
<with-shadow-dom1></with-shadow-dom1>
<br>
<without-shadow-dom1></without-shadow-dom1>

The styling of the div does not pass through the shadow DOM barrier and therefore does not style its internal <div> element. However, with the web component that does not have a shadow DOM, and does not have a barrier, the style passes through and styles the <div> element.

The test class does not pass through the shadow DOM either. It does style the inner part of the second web component, just like before.

The styles from outside the shadow DOM cannot leak inside and effect the inner elements. Not tags, no classes, nothing will pass through that shadow DOM barrier. It can inherit styles though, but more on that later.

Leaking out of Shadow DOM

The shadow DOM stops styles leaking in, but it also stops styles leaking out. To better understand what I mean by this, we need to look at an example. To show this we need two web components, each with their own styles inside, and with one of them using a shadow DOM.

// Set inner HTML (into the shadow DOM)
this.shadowRoot.innerHTML =
  `<style>
    div { background-color: lightblue; }
    .class1 { color: blue; }
    .class2 { color: red; }
  </style>
  <div class="class1">With Shadow DOM (class1)</div>
  <div class="class2">With Shadow DOM (class2)</div>`;

...

// Set inner HTML (without having a shadow DOM)
this.innerHTML =
  `<style>
    div { background-color: lightgray; }
    .class3 { color: orange; }
    .class4 { color: green; }
  </style>
  <div class="class3">Without Shadow DOM (class3)</div> 
  <div class="class4">Without Shadow DOM (class4)</div>`;

Both web components have their own internal styling and will show their two <div> elements differently. Let's see how they are being used.

<with-shadow-dom2></with-shadow-dom2>
<br>
<without-shadow-dom2></without-shadow-dom2>
<br>
<div>DIV text (no class)</div>
<div class="class1">Class 1 text.</div>
<div class="class2">Class 2 text.</div>
<div class="class3">Class 3 text.</div>
<div class="class4">Class 4 text.</div>  

We are showing both the web components, along with some other test <div> elements. If we didn't have the web components then the test <div> elements would not have any styling applied to them. The class1 to class4 classes would do nothing, because they are not defined anywhere. Take a look at what happens in this example.

The first web component, the one with a shadow DOM, shows its two internal <div> elements with blue and red text. The outside test <div> elements (with class1 and class2) have the default black text and are not changed to either blue or red. The shadow DOM has stopped the class1 and class2 styling from leaking out. You will also note that the light blue background color does not leak out to other <div> tags.

The second web component, the one without a shadow DOM, shows its two inner <div> elements with orange and green text. However, the outer test <div> elements (with class3 and class4) have been changed, and show their text in orange and green. Without a shadow DOM, those class styles have leaked outside and have changed other elements. The light gray background color has also leaked out and changed all the outside <div> tags.

Inherited Styles

Some styles can pass through the shadow DOM. These are inherited style properties. The host element, the one that contains the shadow root element (the shadow DOM), can pass whatever styles it has, through the shadow DOM, into the inner elements. Not all CSS properties can be inherited. Let's take a look at an example.

// Set inner HTML (into the shadow DOM)
this.shadowRoot.innerHTML =
`<style>
  .class-inner {
    color: green;
    background-color: lightblue;
    border: 4px solid red;
  }
</style>
<p class="class-inner">With Shadow DOM (with styles)</p>
<p>With Shadow DOM (without styles)</p>`;  

The web component has some internal styles for one of its paragraphs. This sets the text color to green, the background color to light blue and adds a blue border. The other paragraph has no styling. It will use whatever styles it inherits from its host element. Let's see it being used.

<style>
  .class-outer {
    display: block;
    font-size: x-large;
    color: red;
    background-color: lightgray;
    border: 4px solid blue;
  }
</style>
<p>...</p>
<with-shadow-dom3 class="class-outer"></with-shadow-dom3>
<br>
<with-shadow-dom3 style="color: gold; font-style: italic;"></with-shadow-dom3>

For the first instance of the web component we style it with a class. The host element is given a large font, a red color, a background color and border. The second instance uses an inline style. The color is gold and the font is styled italic. Let's see it up and running.

You will see that in both instances the size of the font and its style (italic) has been passed through the shadow DOM into the inner elements. The color of the text is also passed through.

In the first instance the host is given a blue border. Borders are one of those CSS properties that are not inherited. Therefore all the inner elements of the web component are not given the same border property.

When you give an element a style that matches one of the inherited properties, then the internal style will overrule the inherited one. You can see this with the class-inner styles. It overrules the text color property with a green color value and a light blue background color.

Host

The web component element that holds the shadow root (shadow DOM) element inside it, is known as the host. You can style this host element from outside like you would any other tag, using a class or an inline style. You can also style the host element from within the shadow DOM. You tell the browser how the hosting element should be styled outside of the shadow DOM. This can be done using a number of different CSS selector commands.

Let's take a look at an example and then we will go through them one at a time. The inner HTML with its styles look like this.

// Set inner HTML (into the shadow DOM)
this.shadowRoot.innerHTML =
`<style>
  :host {
    background-color: red;
  }
  :host([attribute-set]) {
    background-color: lightblue;
  }
  :host([attribute-value=good]) {
    background-color: gold;
  }
  :host([attribute-used]:not([attribute-not-used])) {
    background-color: green;
    color: white;
  }
  :host(.class-outer1) {
    background-color: lightgreen;
  }
  :host-context(.class-outer2) {
    background-color: darkorange;
  }
  :host-context(h2) {
    background-color: violet;
  }
</style>
Host Example.`;  

The style will change the background color depending on a number of different conditions. We will look at each one in more detail, but first lets see how the web component is being used.

<style>
  .class-outer {
    font-style: italic;
  }
  host-example {
    font-size: x-large;
  }
</style>
<p>Example of different host styles.</p>
<host-example></host-example> Host without styles
<br>
<host-example style="background-color:lightgray;"></host-example> Host with inline styles
<br>
<host-example attribute-set></host-example> Host with attribute
<br>
<host-example attribute-value="good"></host-example> Host with attribute value
<br>
<host-example attribute-used></host-example> Host with attribute used and another attribute not used
<br>
<host-example class="class-outer1"></host-example> Host with class
<br>
<div class="class-outer2">
  <host-example></host-example> Host with parent with class
</div>
<h2>
  <host-example></host-example> Host with parent of tag H2
</h2>

We use the host-example tag in a number of different ways. Each one shows how we can style the host depending on a number of different conditions. Take a look at the example up and running.

:host

This is used to select the host element without any other conditions.

:host {
  background-color: red;
}
<host-example></host-example> Host without styles

Here we are saying that we want the host element to have a background color of red by default. If the web component tag does not have its background color set to anything, then it will use this default red color. But, if you do set the background color of the <host-example> web component, then what happens?

<host-example style="background-color:lightgray;"></host-example> Host with inline styles

Here we are setting the background color to light gray using an inline style. The result is that the element does not use the default color of red, but has its background color overridden by the light gray inline style.

:host([attribute-set])

The next style is related to the host element having an attribute. It doesn't matter what the value of the attribute is, only if it exists or not.

:host([attribute-set]) {
  background-color: lightblue;
}
<host-example attribute-set></host-example> Host with attribute

You need to use the (...) parentheses followed by the normal CSS selector for checking attributes. In this case, if the host element has the attribute attribute-set, then we use the background color of light blue.

:host([attribute-value=good])

The next style is just like the last one but we want to look out for an attribute that has a given value.

:host([attribute-value=good]) {
  background-color: gold;
}  
<host-example attribute-value="good"></host-example> Host with attribute value

This time we are looking for the attribute attribute-value with the value of good. If found then we set the background color to gold. As you can see, the HTML has the <host-example> tag with the attribute set to the value we are looking for.

:host(:not([attribute-not-used]))

The next allows you to use the :not(...) pseudo class.

:host([attribute-used]:not([attribute-not-used])) {
  background-color: green;
  color: white;
}  
<host-example attribute-used></host-example> Host with attribute used and another attribute not used

Here we are looking to see if the attribute is not used. For this example I have had to also make sure the attribute-used is used, otherwise all the other examples would be using the same CSS values.

:host(.class-outer)

The next style is looking to see if the host element has a class with a given name.

:host(.class-outer) {
  background-color: lightgreen;
}  
<host-example class="class-outer"></host-example> Host with class

Here we are looking to see if the class class-outer has been applied to the host element. If it has the given class name then the background color is set to light green.

host-context(…)

This is used to check if the host element exists within some given type of parent.

:host-context(.class-outer2) {
  background-color: darkorange;
}
<div class="class-outer2">
  <host-example></host-example> Host with parent with class
</div>

For this style we are looking to see if the host element has a parent that has been given a class name of class-outer2. If this is found then it is given the background color of dark orange.

:host-context(h2) {
  background-color: violet;
}  
<h2>
  <host-example></host-example> Host with parent of tag H2
</h2>

The final style is looking to see if the host element is somewhere inside a <h2> tag. If it is then it is given the background color of violet.

Host Display

By default a web component is given the display style of inline. But it is best to set the host element to have a different default display style, either block or inline-block. Not doing so may create some issues for you. Let's look at an example of one of these problems.

<style>
  :host {
    background-color: lightblue;
    border: 4px solid blue;
    font-size: x-large;
  }
</style>
<div>Host display defaults to inline.</div>

This is the inner HTML with some host element styling. It does not set the display style which will mean that it will use the default display style of inline.

<style>
  :host {
    display: inline-block;
    background-color: lightblue;
    border: 4px solid blue;
    font-size: x-large;
    }
</style>
<div>Host display inline-block.</div>

This is the inner HTML of the second web component and it has set its host element's display style to inline-block.

<style>
  :host {
    display: block;
    background-color: lightblue;
    border: 4px solid blue;
    font-size: x-large;
  }
</style>
<div>Host display block.<div>

This is the inner HTML of the final web component and it has changed the host's display style to block. These 3 web components are used together in the following way.

<host-display-default></host-display-default>
<br>
<host-display-inline-block></host-display-inline-block>
<br>
<host-display-block></host-display-block>  

The first web component, with the tag <host-display-default>, is not having its display style set to anything, therefore its default value is inline. The second and third web components, with the tags <host-display-inline-block> and <host-display-block>, have both changed their display's style, by setting the host's display style value, to inline-block and block. Take a look at the end result.

The first web component does not display the border correctly. Having a display style set to inline can create some issues with other styles.

The second web component, with its display style set to the value inline-block, is behaving like any other inline block tag (<span> for example). The third web component is behaving like a block element (<div> for example), because its display style is set to block.

All these default display styles can be bypassed by changing the display style on the tag element itself, which, if you remember, is the host element.

Slotted

You can have some control over how the slotted elements are styled, but there are limitations. Not every part of a slotted element can be styled from within the web component. Before we look at what can be done, let's look at the example that shows the different options we have.

<style>
  ::slotted(*) {
    background-color: red;
    font-size: x-large;
  }
  ::slotted(.slot-class) {
    background-color: gold;
  }
  ::slotted([attribute-set]) {
    background-color: lightblue;
  }
  ::slotted([attribute-value=good]) {
    background-color: darkorange;
  }
  ::slotted(p) {
    background-color: lightgreen;
  }
  ::slotted([test7])::before {
    content: '<';
    background-color: aqua;
  }
  ::slotted([test8])::after {
    content: '>';
    background-color: pink;
  }
  ::slotted([test9]:hover) {
    background-color: gray;
    color: white;
  }
</style>
<slot></slot>`;

This is the inner HTML of our test web component. It has a number of different styles that are used to change the background color of different slots, depending on how the slot is configured. It also has a default slot, where all the test slotted elements will be shown.

<style>
  .slot-class {
    font-style: italic;
  }
</style>
<p>Example of different slotted styles.</p>
<slotted-example>
  <div>Slotted Test 1</div>
  <div style="background-color: lightgray;">Slotted Test 2 (inline style)</div>
  <div class="slot-class">Slotted Test 3 (with class)</div>
  <div attribute-set>Slotted Test 4 (with attribute)</div>
  <div attribute-value="good">Slotted Test 5 (with attribute value)</div>
  <p>Slotted Test 6 (with P)</p>
  <div test7>Slotted Test 7 (with ::before)</div>
  <div test8>Slotted Test 8 (with ::after)</div>
  <div test9>Slotted Test 9 (with :hover)</div>
</slotted-example>

This is how the web component is being used, with a number of differently configured slots. Take a look at the example in action.

Each slot has a different background color. Each of the CSS selectors being used to style the slotted elements are using the same ::slotted(...) command. The information inbetween the parentheses controls how the CSS selector works to find the required slotted elements. It is basically saying that it wants to check each slotted element for some other condition. Let's go through each one to better understand what is happening.

::slotted(*)

The first time this is used is to look for all the slotted elements and to give them a background color of red and a large font size. This is being used as a default, so all slotted elements are automatically having their background color and font size set. Because the first slotted element in the example (Slotted Test 1) does not match any of the other CSS selector options, it will have its background color set to red by default.

In the example, for the second test (Slotted Text 2), we are using an inline style to change the background color to light gray. This shows that you can bypass the web component's styling for the slotted elements from the outside, by styling the slotted element directly.

::slotted(.slot-class)

The next CSS selector is looking for slotted elements that have been given a class that matches the one given. This will select all slotted elements that have the class name slot-class applied to it. The slotted element will be given the background color of gold if they match. In the example, the third slotted element (Slotted Text 3), which matches and therefore has the gold background color.

::slotted([attribute-set])

The following CSS selector looks to see if any slotted element has the given attribute or not. It does not look at the attribute's value, only if it exists or not. If a slotted element does have the attribute then it is given the background color of light blue. In the example, the fourth slotted element (Slotted Text 4) matches this condition and therefore has the light blue background.

::slotted([attribute-value=good])

The next CSS selector is similar but this time it is looking for an attribute with a given value. It is looking for the attribute named attribute-value with the value of good. If the slotted element has this then it is given the background color of dark orange. In the example, the fifth slotted element (Slotted Text 5), has the matching attribute with the given value and therefore has the dark orange background color.

::slotted(p)

The next CSS selector is used to check if the slotted element is of the tag type <p> (paragraph). If it is then it is given the background color of light green. In the example, the last slotted element is a paragraph element and is therefore given the background color.

::slotted(...)::before
::slotted(...)::after

The next CSS selector is used to add some limited content before and after the slotted elements by using the element pseudo classes ::before and ::after. You will see that the slotted elements we added did not contain the < or > extra characters elements, but were added in through the CSS pseudo classes.

::slotted(... :hover)

The last CSS selector is used to style the slotted element when the user hovers the mouse over it. You can use the other element state pseudo classes too. In this example if you hover the mouse over the slotted element the background and text colors change.

What you cannot do, is use the CSS selector to select any child elements directly. You can only use it for top level slotted elements, not their children. For example, ::slotted(p > span), will not work.

Named Slots

A web component can have any number of different named slots within. You can select to style each one differently. To see how we style a named slot, we need to look at an example.

<style>
  slot[name=make-bold]::slotted(*) {
    background-color: red;
  }
  slot[name=make-italic]::slotted(*) {
    background-color: gold;
  }
  slot::slotted(*) {
    background-color: lightblue;
  }
  ::slotted(*) {
    font-size: x-large;
  }
</style>
Default: [<slot></slot>]<br>
<b><slot name="make-bold"></slot></b><br>
<i><slot name="make-italic"></slot></i>  

This is the HTML used inside the web component. It has a number of named slots and styling for each one. Let's look at it being used before we explore it in more detail.

<slotted-name>
  <span>Default span</span>
  <span slot="make-bold">Slotted make-bold.</span>
  <span slot="make-italic">Slotted make-italic.</span>
</slotted-name>

We are using the web component with 3 slots inside. A default slot, one with the slot name make-bold, and another with the slot name make-italic. See the example up and running below.

slot[name=make-bold]

The slot part is used to look for a slot within the web component. The [...] square brackets are used to look for an attribute, and in this case, we are looking for the name attribute with the value of make-bold. The last ::slotted(*) part is used to style all the slotted elements inside the make-bold slot. All the slotted elements linked to the make-bold slot will have the red background color.

slot[name=make-italic]

The next selector is similar, but it is looking for the make-italic slotted elements. All the slotted elements that are put into the make-italic slot will end up having a gold background color.

slot::slotted(*)

For default slots we need to use the slot selector without an attribute name. There are no square brackets following the slot part. Here we want all the default slotted elements to have the light blue background color.

If you want to select all slots, named or the default one, then you can use the ::slotted(*) selector, without using the slot prefix. This will select all slotted elements.

Default Slots

Using only text with default slots can create some problems. You can use any element tag as a default slot, and you can use just plain text, but you cannot style the text on its own. To show this we need to create an example to help us understand the issue.

<style>
  ::slotted(*) {
    font-size: x-large;
    font-style: italic;
    background-color: gold;
  }
</style>
<slot></slot>`;

This inner HTML of the web component styles the default slot, giving it a gold background color and changing the size and style of the font.

<default-slot-style>
  Default slot text
  <div>Default slot element</div>
  <div style="background-color: lightblue;">Default slot element (with inline style)</div>
</default-slot-style>

We use the example web component with some default text and two default slot elements. The problem slot is the default text, as there are no tags between it, and it is seen as a text node and not an element. Let's take a look at what happens.

The default text on its own is not styled in any way. The CSS selector was unable to see it as an element. The other two default slot elements are formatted without any issues. The background color of the last default element has been bypassed by the inline style, but the other one has been styled by the internal CSS selector style.

There are other problems using text as the default slot too. It may be best, were possible, never to design your web components to work with default slots without elements.

CSS Variables

You can use CSS variables like parameters to style the different parts within your web component. They can pass through the shadow DOM barrier just like other inherited style properties. You can declare the CSS variable, --text-color for example, and then use it within the web component styling, like color: var(--text-color); Let's look at an example to see what can be done.

<style>
  span {
    background-color: var(--background-color, lightblue);
    color: var(--text-color, black);
  }
</style>
<span>Using CSS variables</span>`;

Here we are setting the style for all <span> tags within the web component. Both the background color and the text color are set using a variable. The background color style is looking for the --background-color variable, which must have been set outside the web component somewhere, but if it wasn't then it will use the default background color of light blue. We use the var(...) CSS function to get the variable. The second parameter is the default value, if the first parameter did not exist.

<style>
  .class-variables {
    --background-color: gray;
    --text-color: white;
  }
</style>
<p>Example of using CSS variables with styling.</p>
<css-variable></css-variable> Using defaults
<br>
<css-variable class="class-variables"></css-variable> Using a class
<br>
<css-variable style="--background-color: gold; --text-color: red;"></css-variable> Using inline

We are using the web component in 3 different ways. The first method is to see how it works with no variables set. It should fall back to using the default color values. The second way is to have the tag use a class that has the variables set. The third method is to set the variables using an inline style. Take a look at the final result.

You can see the first time the web component is being used it is without any variables so that the background color is the default light blue. The second time it is being used it is with the class, which has the added the variables and therefore has had the background color changed to light gray. And the final time it is used, the inline style has set the variables and changed the background color to gold.

CSS variables can be a great method of configuring a group of web components to use some type of template coloring. They can be used with any style, not just for colors.

Styling Internal Parts

There is another method of allowing the user of a web component to style the inner parts. The shadow DOM stops the inner parts from being effected by outside styles, but if you want some parts to be styled then there is a method to do it. To allow an inner element to be styled from outside, you need to give that element the attribute part and give it a name. Let's take a closer look with an example.

Top part text
Middle part text
Middle part text (highlight)
Bottom part text
`;

This is the inner HTML of the example web component. There is a section we have given the part attribute a value of top. We want to allow a style outside of the web component to change how this element looks. The same has been done for the bottom part. There are two middle sections but one has an extra highlight part value.

<style>
  part-example::part(top) {
    background-color: red;
  }
  part-example::part(middle) {
    background-color: lightblue;
  }
  part-example::part(highlight) {
    background-color: darkorange;
  }
  part-example::part(bottom) {
    background-color: lightgreen;
  }
  part-example::part(middle highlight) {
    font-size: x-large;
  }
</style>
<p>Example of using CSS part to style inner parts of web component.</p>
<part-example></part-example>

We are only using the web component once. We are styling the different inner parts using the CSS selectors. We need to use the ::part(...) selector, with the name of the part inside. The first one sets the top part to have a background color of red. The last style selector is looking for both the middle and highlight parts combined. You can use more than one part name inside the selector.

This method can be useful in allowing some parts of your web component to be customized, to still function in the same way you want, but to take on the style and look of the rest of the web site.

Using EM Sizing

In styling there are a number of different sizing units, px, percent, rem for example, that you use to set the size of different properties. One of these that is useful when it comes to web components is the em unit. This gives the relative size of the current font size. So 2em would be twice the size of the current font. If the current font was 16 points then 2em would convert into 32 points in size.

<style>
  .smaller-font {
    font-size: 0.75em;
  }
  .larger-font {
    font-size: 1.25em;
  }
</style>
<div class="smaller-font">Smaller font text.</div>
<div>Normal font text.</div>
<div class="larger-font">Larger font text.</div>

In this example the web component has 3 text areas, from smaller text, to normal sized text, and then ending with a larger text size. It has used the em unit to set the size of the font. The smaller font will be 75% of the current font size. The larger font will be 125% bigger than the current font size.

<em-font-size></em-font-size>
<br>
<em-font-size style="font-size: x-large;"></em-font-size>

We are testing the web component by first using it with the default font size (medium) and then using it with an inline style changing the font size to x-large. Take a look at the result.

You will see that the font size used for the text inside the web component are related to the current font size of the host element. This can be a useful method for allowing the user of your web component to change the overall size of the inner parts. You could create a custom input web component, which would have the inputted text adjust to the size given to the host element. It could use the same font size the host element was setup with.

Missing Slots

A web component can be created with slots, areas where the user of the web component can insert their own HTML. It could be that one of the slots is not used or is not required. When this happens you may want to change how some parts are styled. For example, if you had a panel web component, that allows for a footer section, but if no footer slot is used, we should make sure no footer parts are shown.

Sadly, you can not use CSS selectors to work out if a slotted area is empty or not. However, there are ways around this. We need to check for slot changes, and when they happen, get the list of assigned elements to the slot, and if there are none, set some attribute somewhere indicating that the slot is empty. Then we can use CSS to adjust how the inner elements are shown. This can seem complex, so lets take a look at an example.

<style>
  #default[empty] {
    display: none;
  }
  #make-bold[empty] {
    display: none;
  }
  #make-italic[empty] {
    display: none;
  }
</style>
<div id="default" empty>
  Default: [<slot id="default-slot"></slot>]
</div>
<div id="make-bold" empty>
  Bold: <b><slot id="make-bold-slot" name="make-bold"></slot></b>
</div>
<div id="make-italic" empty>
  Italic: <i><slot id="make-italic-slot" name="make-italic"></slot></i>
</div>  

Take a close look at the inner HTML of this example web component. There are 3 areas. The first is used to show default slots. Note how it is inside another element. If there are no default slots then we want the element, the one the slot is inside of, to be hidden, not just the slot element itself. The same idea is being used with the make-bold and make-italic slots.

connectedCallback() {
  // Get elements
  this._defaultSlotElement = this.shadowRoot.getElementById('default-slot');
  this._makeBoldSlotElement = this.shadowRoot.getElementById('make-bold-slot');
  this._makeItalicSlotElement = this.shadowRoot.getElementById('make-italic-slot');

  // Add slot change event
  this._defaultSlotElement.addEventListener('slotchange', this._defaultSlotchange);
  this._makeBoldSlotElement.addEventListener('slotchange', this._makeBoldSlotchange);
  this._makeItalicSlotElement.addEventListener('slotchange', this._makeItalicSlotchange);
}  

When the web component is connected, we need to get the slot elements and for each one add an event listener for the slotchange event. Whenever the slot is added or removed from the slot this event will be fired.

_defaultSlotchange(event) {
  // Get default element
  const defaultElement = this.shadowRoot.getElementById('default');

  // Get default assigned elements
  const assignedElements = this._defaultSlotElement.assignedElements();

  // If no slotted elements
  if (assignedElements.length === 0) {
    defaultElement.setAttribute('empty', '');
  } else {
    defaultElement.removeAttribute('empty');
  }
}  

This is the event for when the default slot changes. The other two slots have their own event function. The first thing we do is to get the outer element that we want to change if the default slotted element list is empty or not. Then we get the list of assigned elements that the slot is containing. Now, if the list is empty, then there are no default elements slotted into the default slot, so we give the outer element's attribute empty. If there are default elements then we make sure the outer element does not have the attribute.

If you go back to the inner web component's HTML and the style section, you will see that the default outer element will be hidden if it has the empty attribute. This way, the whole default element block will be hidden if there are no default slotted elements added.

<missing-slots>
  <span slot="make-italic">Some italic text</span>
  <span>A default element</span>
</missing-slots>
<br>
<missing-slots>
  <span slot="make-bold">Some bold text</span>
  Some default text
</missing-slots>

In this example we are showing the same web component twice, once with no make-bold slots, and then again but this time with no make-italic slots. Both have some default slots, but the first is using an element, and the second is using plain text.

You will notice in the example that the bold section in the first part is not shown, and the italic section in the second part is not shown either. If the slot is missing then the block it exists in will be hidden.

You will also see that there is an issue with the default slot. In the first case, where the default slot was using an element, everything is shown as you would expect. But in the second case, where it is not using an element, but plain text instead, it is not showing the default text. This is because we cannot see if the default slot contains anything, using the assignedElements function, when it only contains plain text, because it is not seen as an element.

There may still be some method of working with plain text in the default slot but it may require a lot of exact work.

Not Defined

Using slots in a web component, having HTML inside the tags, can create a problem if it takes time to download the JavaScript file it is stored in. Take a look at the example below.

<my-tag-with-slots>
  <div slot="some-slot">
    <p>Some text to show inside the slot.</p>
  </div>
</my-tag-with-slots>

We are using a web component and inside we have slotted HTML tags and text. When the browser loads up the HTML, it will show the slotted HTML without first loading the JavaScript file for the web component, and as a result, it will show it without it being slotted or styled as the web component will have designed it. It will look odd for a second, until the browser loads in the web component and process it.

Until the web components code has been loaded, the tag linked to it will be seen as not defined. We can use a CSS selector statement to hide all undefined tags in the HTML file until the code linked to it has been loaded and processed. Let's look at an example of how to do this.

<style>
  .hide-until-defined:not(:defined) {
    display: none;
  }
</style>
<p>By default</p>
<not-defined>
  <div>Default slot text 1.</div>
  <div>Default slot text 2.</div>
</not-defined>
<br>
<p>With :not(:defined) used</p>
<not-defined class="hide-until-defined">
  <div>Default slot text 2.</div>
  <div>Default slot text 3.</div>
</not-defined>

Here we are going to show the same web component, with default slots, with the first without doing anything to hide the slot HTML, and the second with the hide-until-defined class. In the example below there will be a button to load in the web component's JavaScript file. This is used to simulator a delayed loading.

To hide the web component until it is defined, we use the :not(:defined) CSS selector command. We use this to state that while the element tag is not defined, we want to hide the element from the user.

In the example you can see that the slot HTML in the first instance is shown, but the second part is hidden. Pressing the load button will show the second part, once the web component is loaded and processed.

It is a good idea to add the following CSS in a location where it will be loaded quickly.

*:not(:defined) {
  display: none;
}  

It will make sure that any tag, any web component, that is not loaded, not defined, will not be shown, until the JavaScript file it is linked to is loaded and processed.