<slot/>ing in some tips

westbrook
5 min readApr 15, 2018

--

Our final <custom-header/> code structured as a JS template literal.

I’ve been working with Web Components for almost four years now with the help of the Polymer Project from Google. One powerful tool that you get when working with web components is the <slot/> element which allows you to denote places in your component’s <template/> where you’d like to inject customizable content. I’ve occasionally used this in Web Components I’ve built and/or implemented, but probably not nearly as much as it deserves. That being so, when I do apply content via <slot/> I often have to run some quick experiments to ensure what I see as being possible aligns with what is actually in the specification.

While I know it’s unavailable to me, I’m always interested in projecting the external content into two locations in my template, like so:

<template>
<slot name="thing-i-want-twice"></slot>
<slot name="thing-i-want-twice"></slot>
</template>

I’d be a very happy engineer if one day I went to implement this via the following code:

<custom-element>
<span slot="thing-i-want-twice">Show me twice</span>
</custom-element>

And it just worked. However, a one-to-many relationship is not part of the <slot/> implementation. I hope that by my pain I can save you some.

In its simplest form, if a custom element with the name <element-with-slot/> were to have a template like so:

<template>
The slot content: <slot></slot>
</template>

And content applied as such:

<element-with-slot>
<span>Put me in the slot</span>
</element-with-slot>

It would render the following flattened tree:

<element-with-slot>
The slot content: <span>Put me in the slot</span>
</element-with-slot>

When working with slots this way, you also have the possibility of applying a many-to-one relationship that would allow you apply the follow content to the same <template/>:

<element-with-slot>
<span>Put me in the slot</span>
<span>And then me</span>
</element-with-slot>

Such that the the flattened tree would display as:

<element-with-slot>
The slot content: <span>Put me in the slot</span><span>And then me</span>
</element-with-slot>

This feature certainly has questionable value in a text content component like our <element-with-slot/> example. However, if out element with a slot were an ornate header and the content we were injecting there was custom navigation items, there would be a lot more flexibility gained by this technique.

<slot/>ed Navigation Items

<header>
<a href="/index">Home</a>
<a href="/about">About</a>
<a href="/calendar">Calendar</a>
<a href="/contact">Contact</a>
</header>

Imagine that you want to implement a header with the above links. However, being a very modern header, you’d also like it to have logo, some social links, possibly a sign up interface, and most of all you’d like to be able to toggle some of the content in responsive contexts. We could just add these to the above <header/> code, BUT while we’re likely to customize the navigation items between various types of users, the more common elements are likely to appear on all pages. We can achieve this effect by simply taking our <header/> implementation from above and changing it to <custom-header/> like so:

<custom-header>
<a href="/index">Home</a>
<a href="/about">About</a>
<a href="/calendar">Calendar</a>
<a href="/contact">Contact</a>
</custom-header>

Then inside of our new <custom-header/> element we can construct the following <template/>:

<template>
<site-logo></site-logo>
<nav>
<slot></slot>
</nav>
<button>Toggle Nav</button>
<social-links></social-links>
<sign-up-cta></sign-up-cta>
...etc...
</template>

Which keeps the implementation details of our <header/> tucked away in our new element while being able to customize the navigation item arguments that we pass it in individual implementations. <slot/>s also allow us the ability to name them making the act of applying content with them more explicit, as well as the option to outline their default content. Doing both of these things takes this template to another level:

<template>
<site-logo></site-logo>
<nav>
<slot name="navigation-item">
<a href="index">Home</a>
<a href="/about">About</a>
</slot>
</nav>
<button>Toggle Nav</button>
<social-links></social-links>
<sign-up-cta></sign-up-cta>
<slot></slot>
</template>

Notice here that we’ve listed default navigation items for “Home” and “About” that will be replaced when explicit slot="navigation-item" content is injected. There is also a fall through <slot/> that allows any other content that is present in the <custom-header/> element to have a home in the flattened tree. These approaches come together in such a way that:

<custom-header>
<a href="/index" slot="navigation-item">Home</a>
<a href="/about" slot="navigation-item">About</a>
<a href="/calendar" slot="navigation-item">Calendar</a>
<a href="/contact" slot="navigation-item">Contact</a>
<span>Extra Content</span>
</custom-header>

Will render out into a flattened tree of:

<custom-header>
<site-logo></site-logo>
<nav>
<a href="/index">Home</a>
<a href="/about">About</a>
<a href="/calendar">Calendar</a>
<a href="/contact">Contact</a>
</nav>
<button>Toggle Nav</button>
<social-links></social-links>
<sign-up-cta></sign-up-cta>
<span>Extra Content</span>
</custom-header>

These interfaces give our new <custom-header/> element a good bit of flexibility as the interface changes over the lifecycle of its user or the application grows in complexity over time. Remember in the context of both our named <slot name="navigation-item"/> and our catch all <slot/> there is a many-to-one relationship. This allows you the ability to inject any number of properly denoted elements into the element from the the outside knowing they will be delivered to the appropriate <slot/>.

Bonus…

When working with components it can be useful to experiment with various levels of upward and downward abstraction. In the case of our <custom-header/>, it is starting to get pretty busy in there, we’ve assigned the responsibility of things like <site-logo/>, <social-links/>, and <sign-up-cta/> to other components and it is looking about time to abstract the management of the navigation down into an element of its own as well. What follows is a possible template for this type of <site-nav/> element:

<template>
<nav>
<slot name="navigation-item">
<a href="index">Home</a>
<a href="/about">About</a>
</slot>
</nav>
<button>Toggle Nav</button>
</template>

While it is not a requirement, if you chose to give the slot here a name as is shown above, it is important to remember that in order to be able to inject the navigation items from the outside of <custom-header/> you’ll need to ensure the intermediary <slot/> is labeled correctly to pass those items along. In this case, <slot slot="navigation-item"/> will be required:

<template>
<site-logo></site-logo>
<site-nav>
<slot name="navigation-item" slot="navigation-item"></slot>
</site-nav>
<social-links></social-links>
<sign-up-cta></sign-up-cta>
<slot></slot>
</template>

This will allow your elements to pass the slotted content correctly from outside of the parent through to their child/children or however your final element family it structured.

Go Forth and <slot/>

Writing this out for you today has already got me thinking of new ways to apply <slot/>s to components that I work with and/or will be creating in the near future. If this have gotten your creative juices flowing too I’d love to hear about your plans in a response below. Particularly, if there’s something you’ve found that can’t be <slot/>ed, I’d love to save the rest of us some pain through yours. Happier <slot/>ing to all!

Special than to my Lingo Live coach Shannon Palsma for her support in crafting this post.

--

--