Jan's Development Microblog
Go back to blog overview

Time to dive into web components

After years of writing the same code over and over it is time to start building my own library of reusable web component.

After years of rewriting HTML and CSS for multiple platforms, it was time to look for a better solution. At my current workplace, we are using a CMS platform with a CSS framework. However, the issue is that the code is not easily reusable in other codebases. Due to this, I had to reinvent the wheel over and over. I'm kinda done with that, if you understand what I mean. After refreshing my knowledge about HTML web components, it was time to do a deep dive to figure out if this would fix my problem. When writing code, I want to share components between multiple projects without thinking about the code style. It should just work with minimal configuration. Through the years, I have looked at existing HTML frameworks, but they never met my demands. So it was time to build my own and check off all of my boxes.

What are HTML Web Components?

​HTML web components offer a powerful way to create custom, reusable elements for your web pages. They allow you to bundle HTML, CSS, and JavaScript into a single, self-contained unit that can be easily integrated into various parts of a site or even across different projects. For instance, if you're designing a unique button with specific styles and functionalities, you can encapsulate it as a web component. This way, you avoid having to recreate it from scratch each time you need it. The true advantage of web components lies in their ability to maintain encapsulation, ensuring that each component's design and behavior remain independent from the rest of the page. This isolation guarantees consistent performance and prevents unintended interference with other elements on your site.

Why reinventing the wheel?

You might be wondering why I didn't choose an existing web component library. The answer is straightforward. I reviewed many frameworks, but none met my specific needs. Most libraries offer extensive color palettes and complex layout systems, but often, websites only use a few colors and need simple, customizable layouts. I wanted a solution that allows me to select a section type and easily fill it with content, rather than spending time designing layouts from scratch. The building blocks I needed were simply not available in the frameworks I explored.

At the moment of writing this blog I have created seven web components:

  • Card
  • CTAbutton
  • Section container
  • Header card (which I may delete)
  • Mobile menu (header + sidebar)
  • Login form
  • Newsletter signup form

Building those web components give my some headaches as the CSS is scoped doesn’t work as I expected. So I had to learn how to deal with HTML put into a and how to style the web components that are loaded into the shadow DOM. But little by little it got easier to understand. I will share the code of my header-component as this one is the easiest one to understand. This is also the first web component have created.

Example of the Header card​

One of the first web components I developed was this header card. To enhance readability, I initially separated the code into multiple files: HTML, CSS, and JavaScript. This modular approach helps keep the code organized, but I might later consolidate these files into a single one to reduce the number of browser requests. Currently, loading a single web component requires fetching at least seven files. While the HTTP/2 protocol mitigates some of the concerns by allowing multiplexed requests, the total bandwidth used by multiple files is still greater than that of a single file. Addressing this will be a task for the future.

fr-header.html

<link rel="stylesheet" href="/components/fr-header/fr-header.css">

<header>
<div class="title" part="title"></div>
<div class="subTitle" part="subTitle"></div>
</header>

fr-header.css

header {
margin-block-end: var(--padding);
}
.title {
font-size: 3rem;
font-weight: 700;
line-height: 1;
}
.subTitle {
margin-block-start: calc(var(--padding) / 2);
font-size: 2rem;
font-weight: 500;
line-height: 1;
}

fr-header.js

export default class FrHeader extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode:'closed' });
const template = document.getElementById('fr-header');
const instance = template.content.cloneNode(true);

shadowRoot.appendChild(instance);
shadowRoot.querySelector('.title').innerHTML = this.getAttribute('title');
shadowRoot.querySelector('.subTitle').innerHTML = this.getAttribute('subtitle');
}
}

index.html

<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="./statics/css/variables.css">
<script type="module" src="components/components.js"></script>
</head>
<body>
<fr-header
title="Hello, Jan Valkenburg"
subtitle="Welcome back">
</fr-header>
</body>
</html>

I have left the main part out. That is the script that initialized the loading of the web components. This one I keep for myself for now.

What I found out during creating my first web components

While building my first web components i ran into several issues and learned a lot of new things. The thing is that I never really have used web components before. Mostly because I never had a reason to use react or other JavaScript frameworks. Why because I was always HTML first and a website should not be fully JavaScript based as this will hurt the users of a website in multiple ways.

When a web component is placed into the shadow DOM it is (almost) not possible to access it from the outside. You can't even style it from the outside. When you want to change the styling you have to use CSS variables or the CSS pseudo ::part. Let's say we want to change the color of a block we can just use CSS variables when the web component has been setup in the right way.

How to use the ::part CSS pseudo-element

To be able to apply CSS styling to an element within the web component we need to make sure the components' elements contains the property part. It is only possible to apply styling to the element with the part property. You can not get any deeper. I have shared some example code of it here below.

​The ::part CSS pseudo-element represents any element within a shadow tree that has a matching part attribute. - ​MDN

fr-button.html

<a href="#" part="button">    
<slot></slot>
</a>

fr-button.css

.blockStyle1 {
custom-button::part(button) {
color: blue;
}
custom-button::part(button):hover {
color: green;
}
}

How to use the ::slotted CSS pseudo-element

Sometimes we want to style elements that are slotted in the web components. The slotted content is not part of the shadow DOM, but linked within it. We can only add CSS styling to slotted content for the first level. This just works in the same way al the ::part() pseudo as explained above.

The ::slotted() CSS pseudo-element represents any element that has been placed into a slot inside an HTML template (see Using templates and slots for more information). - ​MDN

fr-button.html

<a href="#" part="button">    
<slot></slot>
</a>

fr-button.css

::slotted(span) {
font-weight: 900;
}

index.html

<fr-button type="alternative" url="//google.com">
<span>BOLD</span>
</fr-button>

How to use the HTML slot-element

The HTML slot element is one of the main parts of a web component. Slots allows to slot in external HTML code that should be rendered within the component. A slot element without the name property is the main slot. The HTML linked to a slot cannot be fully controlled with CSS within the web component. It is only to style the first layer of element within the slot using the web component.

​The HTML element—part of the Web Components technology suite—is a placeholder inside a web component that you can fill with your own markup, which lets you create separate DOM trees and present them together. - MDN

<a href="#" part="button">    
<slot></slot>
</a>
​​

In case there is a need for multiple slots we can include the property name to it. This way we can create multiple slots for linked HTML.

<a href="#" part="button">    
<slot></slot>
</a>
​​

Final word

For years I was a bit scapticle about web components, mostly because they were linked with javascript frameworks or complete design languages that never have matches my way of building the web. When is dove into this technology myself I think HTML Webcomponents are a great addiction to the modern web. And they are a good solution for building reusable code. It is not perfect as the rules setup in the web components won't always fit the style of the project code base. For now we will see how good this component library will work in the future.