What the slot?

by Egor Kloos (aka dutchcelt) published on

Web Components. The discussion seems to pop up more than it used to.

Web Components offer nifty features for creating components using JavaScript, CSS, and HTML. They have been available in all major web browsers since 2017. That’s long enough to start using them in production.

Web Components consist of three things that are intended to work together.

  1. Custom Elements
  2. Shadow DOM
  3. Templates and Slots

The great thing is that you can use each of these on their own. For HTMHell and fun, let’s take a closer look at Slots and the <slot> element.

What are slots?

For slots to do their thing, we need a Shadow DOM. MDN on what the Shadow DOM is:

A set of JavaScript APIs for attaching an encapsulated "shadow" DOM tree to an element — which is rendered separately from the main document DOM — and controlling associated functionality. In this way, you can keep an element's features private, so they can be scripted and styled without the fear of collision with other parts of the document.

Slots only exist in the Shadow DOM and are a way to get content from the document (i.e. a web page).
We have, in effect, two types of DOM. A Light DOM (the document) and the Shadow DOM attached to a Custom Element or a basic set of HTML elements.

Yes, you can have a Shadow DOM associated with a <div>, a <section> element, or a <main>!
Slots are rather magical and can prove very useful in bridging these two DOMs.
The best way to explain what a slot is is to show you. This basic example uses slots and a <div> element.

See the Pen Attach a Shadow DOM to a DIV by Egor Kloos (@dutchcelt) on CodePen.

Here is another example

Let’s say you wanted to use a <slot> with a Custom Element and a <template>. For brevity’s sake, I only show the two HTML parts, not the entire Custom Element setup.

Custom element markup

This is the HTML you would write on your web page.

<card-component>
<h2 slot="heading">A heading passed to the named slot 'heading'.</h2>
<blockquote>This will be passed on to the unnamed slot. Lorem ipsum dolor...</blockquote>
</card-component>

Template for your Shadow DOM

The Template is ‘appended’ to the Shadow DOM inside the Custom Element, and the <slot> pulls the content in. The Custom Element now renders the template with the content from the page.

<template>
<a id="linkheader">
<slot name="heading"></slot>
</a>
<div id="content">
<slot></slot>
</div>
</template>

The key is that you can place your <slot> into a more elaborate markup provided by the template.

The crispy bits

Here is a short list of what makes slots great:

  • Content inside the Shadow DOM is not always as accessible as on a normal web page. Slots allow your content to exist in both. The content remains on the page, and the Shadow DOM can access and enhance it.
  • Slots can be placed in any order. Especially with named slots, as we can see in the Codepen above. In the document source, the ‘heading’ is the first element but displayed as the second element through the template.
  • It is easy for machines to index (i.e. search engines and crawlers like chatGPT) because the content is still on the web page. Note: Most search engines now index the Shadow DOM’s content.
  • Content changes on the page are passed on to the Shadow DOM. Slots are live; Ummm, I mean reactive!
  • You don’t need to style the slotted elements as the web page styling already styles them. Styles are passed along with the content.

This all sounds very tasty. But before we choke on an unclosed <article> let’s see what’s whiffy about slots.

The soggy bits

This part requires a bit more explanation of the behaviour of Shadow DOM. Namely, it’s Encapsulation.
CSS-Tricks has an introduction to Web Components and contains a useful part about Shadow Dom

… the shadow DOM works sort of like an <iframe> where the content is cut off from the rest of the document; however, when we create a shadow root, we still have total control over that part of our page, but scoped to a context.

For me, this is the killer feature of Web Components. Nothing goes in or out—script contexts, IDs, styles, etc. Everything is nicely contained and controlled.
But wait, isn’t one of the positives of slotted elements that they smuggle bring their given styles from the document with them to the Shadow DOM? Houston! We have a problem!

So maybe we don’t want those pesky styles from the document. But hey, at least we can just override them in the Shadow DOM. Right!?

Yes, we can! … Sometimes. — Note to reader: Feel free to insert random expletives —

Slotted elements have weak specificity by design. Let me demonstrate with yet another Codepen.

See the Pen Slotted elements are weak by Egor Kloos (@dutchcelt) on CodePen.

In this example, we can’t style the slotted element. This is because the document already applies a style to them. (See the CSS tab in Codepen)
We can force it to work when we add an !important to the ::slotted rule. Even if the document style also adds an !important the ::slotted style still holds up.

/* Light DOM style */
h2 {
color: green !important;
}
/* Shadow DOM slotted styling */
::slotted(h2) {
color: navy !important; /* The colour is navy */
}

Well, that’s great, I mean GROSS!
It's not as bad as you might think as this is a use-case where you do want to reverse the cascade using !important.

However, this means that styling slotted content from within the Shadow DOM always requires !important for each property. Remember that the initial style could matter so be very careful, especially if you don't have control over the content in the document.
For most situations, you should accept that slotted elements keep the styling they're given. Only override when you really need to.

But what if I want to have my cake and eat it?

We effectively only have 1 way to override the style of slotted content for the Shadow DOM in a clean and inclusive way. Select both the shadowed element and the slotted elements from within the document and style it from there.

.card-demo h2 {
color: navy;
}

I confess adding these additional styles to the document is a bit weird. However, bundling these with the main styles is fine in most situations. This also allows document authors to explicitly apply an override when they need to.
Before you do this, you should ask yourself again if you need these additional overrides. You may find, with some consideration, that you can make do without the added complexity.

An alternative

I have a work-a-round specific to my use case with Design System component libraries using Web Components. In this scenario, I have no control nor insight into the web page the library components are loaded. So I sometimes add some extra CSS to strengthen the slotted elements.

Because the component is explicitly being loaded anyway, I can directly insert Constructable Stylesheets to the document—no need to require a separate instruction to the developer to load additional styling. The Web Component can do it for you.

This Codepen demonstrates how you can add overrides to the document using adoptedStyleSheets.

// Overriding the document styling
const sheet = new CSSStyleSheet();
sheet.replaceSync(`
#demodiv {
& h2 {
color: navy;
}
& p {
color: purple;
}
& [slot="subheading"] {
color: steelBlue;
font-size: 1.3333rem;
font-weight: 300;
}
}
`
);
document.adoptedStyleSheets.push(sheet);

This is rather extreme, and I wouldn't necessarily recommend it. However, it is an interesting approach using new techniques like Constructable Stylesheets.

Slots, I love them, and I hate them. I also wouldn't want to go without them.

Regardless if you’ll use Shadow DOM in the future, I hope slots are now a little less of a mystery.

Update (December 10th, 2023):
Initially the described behaviour of !important was incorrect and has been corrected.

About Egor Kloos (aka dutchcelt)

Egor Kloos was a web designer or front-end developer (depending on who you ask) but is now focused on being a UX engineer. Design implementation is the work that bridges the gap between design and development. His passion for Web Components and Design Systems was, ultimately, inevitable.

Blog: dutchcelt.nl
Egor on Mastodon: Mastodon