HTML

A Quick Introduction to Web Components

Web components will be familiar to you if you have worked at all with React. They are custom, replicable pieces of HTML, which can be referred to elsewhere in the code. Web components are their own HTML specification, so you may be surprised they can be used standalone with pure Javascript and HTML. Let's take a look at how to do that.

Imagine we have a status bar item which we reuse in multiple places. The structure of it might look like this:

html Copy
<div class="status-bar"> <div class="size"> <div class="small">-</div> <div class="big">+</div> </div> <div class="status-bar-progress"> <div class="progress"> <div class="progress-percentage"></div> </div> </div> </div>

Imagine having to paste that into multiple places and maintain it so it always looks the same - and what if we change the status bar someday? We will need to go back to all places that status bar appears, and update those too! Web components allow you to reference this piece of HTML in multiple places, and gives it a unique tag, i.e:

html Copy
<status-bar></status-bar>

How do we do that? Let's take a look at how to make your own web components, and how easy it is.

Step 1. Javascript

Yes you guessed it, if you want to make web components, you need to use Javascript. Let's take a quick look at how you might create a very simple DOM element, that we will call 'paragraph'.

javascript Copy
class Paragraph extends HTMLElement { constructor() { super() this.innerHTML = '<p>Hello</p>' } } // define adds a custom element "alpha-paragraph" using the rules defined in the "Paragraph" class customElements.define('alpha-paragraph', Paragraph);
html Copy
<alpha-paragraph></alpha-paragraph>

The output of this will be a paragraph with the word 'Hello' inside of it. Web components depend on the class structure. We use constructor() here to indicate what will happen when the tag loads.

Other functions

Along with constructor, we can add other functions to a class like connectedCallback() which is fired when the DOM element is appended to a page, and attributeChangedCallback() which is fired when an attribute of the element changes, giving us some flexibility:

javascript Copy
class Paragraph extends HTMLElement { constructor() { super() this.innerHTML = '<p>Hello</p>' } connectedCallback() { // ... } attributeChangedCallback() { // ... } } customElements.define('alpha-paragraph', Paragraph);

Step 2. Expand Functionality

Now we have a way to create a simple element, attach callback events to it, and attach events to attribute changes, let's consider CSS. When making a standalone and clonable web component, we want it to appear physically the same in multiple places. For that it needs its own custom CSS. How do we add CSS to a web component?

To do that, we need to use something called the shadow DOM. These are essentially items appended to only one specific item. We can use this for web components by enabling the shadow DOM, and attaching our CSS to it.

Custom Element with specific CSS

javascript Copy
class Paragraph extends HTMLElement { constructor() { super() // Attach shadow DOM let shadow = this.attachShadow({mode: 'open'}); // Append our Paragraph shadow.innerHTML = '<p>Hello</p>' // Add in our CSS let style = document.createElement('style'); let elCss = style.textContent = ` p { color: red; font-size: 1.25rem; } `; // Append our CSS shadow.appendChild(style); } } customElements.define('alpha-paragraph', Paragraph);

In the above example, any alpha-paragraph element would have a paragraph which is red and has a font size of 1.25rem.

All paragraphs outside alpha-paragraph will not be red, so you can style your other CSS independently.

Step 3. Combine with the template tag

Instead of hardcoding our web components into Javascript, we can use HTML tags instead. HTML has two tags we can use here, template and slot.

A template tag refers to the overall web component, while a slot is a small part of that template which can be altered. An example of a template tag looks like this:

html Copy
<template id="alphaParagraph"> <slot name="paragraph-text"><p>Default Text</p></slot> <p>Another, unchangeable paragraph</p> </template>

The slot on the 2nd line refers to something we can change. In my opinion, we don't really need to use the template tag, but slots are useful. We can update our alpha-paragraph to have a slot by changing the innerHTML:

javascript Copy
class Paragraph extends HTMLElement { constructor() { super() // Attach shadow DOM let shadow = this.attachShadow({mode: 'open'}); // Append our Paragraph shadow.innerHTML = ` <slot name="paragraph-text"><p>Default Text</p></slot> <p>Another, unchangeable paragraph</p> `; // Add in our CSS let style = document.createElement('style'); let elCss = style.textContent = ` p { color: red; font-size: 1.25rem; } `; // Append our CSS shadow.appendChild(style); } } customElements.define('alpha-paragraph', Paragraph);

Then we can replace the slot with our own custom element using the 'slot' attribute in our HTML. In the below example, the whole slot is replaced with a paragraph element containing the text 'Custom Text'.

html Copy
<alpha-paragraph color="blue"> <p slot="paragraph-text"> Custom Text </p> </alpha-paragraph>

Step 4. Attributes

Since all HTML elements can have attributes, we can access them using the getAttribute function. As such, we can easily rewrite our code to have custom colors:

javascript Copy
class Paragraph extends HTMLElement { constructor() { super() // Attach shadow DOM let shadow = this.attachShadow({mode: 'open'}); // Append our Paragraph shadow.innerHTML = ` <slot name="paragraph-text"><p>Default Text</p></slot> <p>Another, unchangeable paragraph</p> `; // Our custom color let color = 'red'; if(this.getAttribute('color') !== null) { color = this.getAttribute('color'); } // Add in our CSS let style = document.createElement('style'); let elCss = style.textContent = ` p { color: ${color}; font-size: 1.25rem; } `; // Append our CSS shadow.appendChild(style); } } customElements.define('alpha-paragraph', Paragraph);
html Copy
<alpha-paragraph color="blue"> <p slot="paragraph-text"> Custom Text </p> </alpha-paragraph>

By putting it all in our Javascript, we ensure we can import this web component elsewhere easily. A demo of our final web component can be seen below:

Last Updated Tuesday, 4 May 2021