A journey to writing vanilla web components

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 web component

The specs. The 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
Where to start…? Ah yes.

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>
`
Me, inserting those reluctant HTML tags

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>
`
Me, adding those CSS styles.

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 !

Well, that was quick.

Full demo here:

Beautifully hand-written code <> awesome result. Sometimes.

Front-end Developer; I‘m not sure who I am.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store