A journey to writing vanilla web components
Part 2: Using slots
If you come from the React world, slots are an approximate equivalent of render props, although they are not functions 😅
Today, we’ll write a <page-layout />
web component
The specs. The <page-layout />
component :
- can have an aside part, piloted by an attribute
- must have in its layout: a header, a footer, a main part, and optionally an aside part

Step 1/4 − The skeleton
// page-layout.jsexport const PAGE_LAYOUT_TAGNAME = 'page-layout';const template = document.createElement('template);
template.innerHTML = `
…
`export class HTMLPageLayoutElement extends HTMLElement {
static get observedAttributes () {
return [];
// I don't need to observe the aside attribute since the display
// is piloted by the CSS
}
get aside () {…}
set aside (value) {…} constructor () {…}
attributeChangedCallback (name, previousValue, nextValue) {…}
}
Step 2/4 − The template
Let’s create a template using the HTML syntax. Notice how we add the four slots “header”, “aside”, “main” and “footer”.
// custom-chip.js
…
template.innerHTML = `
<div class="page">
<div class="header">
<slot name="header"></slot>
</div>
<div class="content">
<div class="aside">
<slot name="aside"></slot>
</div>
<div class="main">
<slot name="main"></slot>
</div>
</div>
<div class="footer">
<slot name="footer"></slot>
</div>
</div>
<style>…</style>
`
…

Step 3/4 − Add the layout style
Let’s add simple CSS rules to shape our layout. We’ll consider having a fixed header and a sticky aside/sidebar:
// custom-chip.js
…
template.innerHTML = `
…
<style>
:host {
--header-h: 2em;
display: block;
}
.page {
min-height: 100vh;
}
.header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: var(--header-h);
border-bottom: 1px solid #888;
}
.content {
margin: auto;
padding: 1em 0;
margin-top: var(--header-h);
display: flex;
justify-content: space-around;
max-width: 40em;
min-height: 100vh;
}
.aside {
flex: 1;
position: sticky;
top: calc(var(--header-h) + .25em);
align-self: start;
display: none;
/* hides the aside if there's no "aside" attribute on the
host node */
}
:host([aside]) .aside {
display: block;
}
.main {
flex: 3;
}
</style>
`
…

Step 4/4 − Use the page-layout component
Here’s the HTML:
<!-- index.html -->
…
<body>
<div id="app">
<page-layout aside>
<header slot="header">…</header>
<aside slot="aside">…</aside>
<main slot="main">…</main>
<footer slot="footer">…</footer>
</page-layout>
</div>
<script type="module" src="main.js"></script>
</body>
And… we’re done !
