The year 2024 might be a big one for web components and I for one am excited about this. Truth be told - a few months ago - I barely knew anything about them as I thought they were just this niche thing floating around on the web. But a lot of buzz is being created. So I wanted to learn more about them. I can now proudly say I published my first (useless) web component on npm - a Valentines special - for-love element.

A two-part post on web components

This article will be split into two parts. In this first section, I’d like to cover the basics of web components, while the second part will serve as a tutorial on creating my first (useless) component.

For those who are utterly clueless about web components - no need to feel ashamed; I’m catching up myself. In this article, I want to delve into the theory and some basic terminology that was complete gibberish to me until a few months ago. One of the key aspects of my blog is documenting the things I learn, with the hope that others can also benefit from it. While there might be better in-depth tutorials available on this subject, I’ll write about some basics and include some recommended reads at the end of this article.

If you do not care about the basics and lingo and just want to hop over to the component I created, that’s fine.

The for-love-element on GitHub

What are web components?

One of my favorite weekend activities is opening a box of LEGO Classic with my daughter and simply building things—sometimes following the booklet, sometimes relying on our imagination. When we search through the LEGO boxes for pieces, we occasionally come across some that are still connected, making it easier for us to avoid starting everything from scratch. It’s perfect; you’ve found yourself a component that you can easily reuse.

This is a perfect analogy for web components; they’re like building blocks for websites, or perhaps even groups of building blocks.

I’m sure that this analogy has been made a lot in the past as I did a quick Google search and got a few hits, but it’s just perfect for it so I’m shamelessly re-using it here. (See what I did here? The analogy just became a component)

Let’s start with the lingo: Custom Elements

Instead of sticking with standard tags like <div> or <button>, we can design your own tags, such as <my-button> or <for-love>. These tags represent unique components that we can use and reuse across our website or web app. It’s like having your own arsenal of tools perfected to your needs. Custom elements always need to have a hyphen inside of them to distinguish them from standard HTML elements and to follow the naming conventions set by the Web Components specification.

The main reasoning behind the hyphen is to avoid conflicts - reducing the chance of naming conflicts with standard HTML elements. Since standard HTML elements don’t contain hyphens (and now of course, never will).

Light and Shadow DOM

Here is where it got a bit complicated for me at first, but this is kinda the best way for me to learn/visualize it:

The Shadow DOM is like a hidden room for web elements. Let’s say you have a workspace where you’re building different parts of a website. Some parts need to do their job without affecting the rest. The Shadow DOM provides an encapsulated space that does just that. It’s like putting a transparent curtain around a specific area. Elements inside this “hidden room” can have their own styles, scripts, and structure without interfering with the styles and scripts of the main page or other elements. Those other elements we’re referring to are what we call the Light DOM.

I can’t remember where I heard that transparent curtain analogy, I didn’t invent it, but it’s the one that stuck with me. That and this simple example of a web component that has an output “This is content inside the Shadow DOM”:

<div>
  <p>This is content outside the Shadow DOM, thus "Light DOM".</p>
  <my-element></my-element>
</div>

<script>
    // Create a new custom element
    class MyElement extends HTMLElement {
      constructor() {
        super();

        // Create a Shadow DOM for this custom element
        const shadow = this.attachShadow({ mode: 'open' });

        // Create a paragraph inside the Shadow DOM
        const paragraph = document.createElement('p');
        paragraph.textContent = 'This is content inside the Shadow DOM.';

        // Append the paragraph to the Shadow DOM
        shadow.appendChild(paragraph);
      }
    }

    // Define our custom element
    customElements.define('my-element', MyElement);
  </script>

The Shadow DOM has some very specific characteristics:

Encapsulation:

Inside of the Shadow DOM, your styles and scripts are not affected by the outside world. This behavior helps to prevent your styles from accidentally messing with other elements on your webpage. Outside CSS won’t have any effect on it except for custom properties.

Composition:

The Shadow DOM introduces slots, little placeholders where you can slide in your own content. This means you can customize the insides of your elements without messing with the original design. This is so powerful and let me tell you why:

In the following example, We will slot an <h2> and <p> inside of a web component. The only thing that it will do is place an <hr> between them (I know, not something you would use web components for, but it’s a simple example).

 <my-card>
    <!-- Content placed inside the slots -->
    <h2 slot="title">Card Title</h2>
    <p slot="content">This is the content of the card.</p>
  </my-card>

  <script>
    class MyCard extends HTMLElement {
      constructor() {
        super();

        // Create a Shadow DOM for this custom element
        const shadow = this.attachShadow({ mode: 'open' });

        // Create a container for the card content
        const cardContainer = document.createElement('div');

        // Create slots for title and content place hr between them
        cardContainer.innerHTML = `
          <slot name="title"></slot>
          <hr>
          <slot name="content"></slot>
        `;

        // Append the card container to the Shadow DOM
        shadow.appendChild(cardContainer);
      }
    }

    customElements.define('my-card', MyCard);
  </script>

Now imagine JavaScript fails, the page would still be showing the slotted content that is provided inside of the web component as this is just some light DOM and the only thing missing would be the <hr>. Even if you would have to support a browser that doesn’t support this, it could still be a progressive enhancement.

Accessibility:

Accessibility features are a shared language. Elements inside the Shadow DOM can understand and speak the same accessibility language as elements outside. What’s even more is that it can really help us in some of the harder accessibility use cases such as tabs. A great collection of web components that does just that can be found at genericcomponents.

Separation of Concerns:

Shadow DOM lets you neatly arrange your structure, styles, and behavior in their own compartments. Imagine having a library of web components for all those repeating tasks: accessible tabs, image zooms, and animatable dropdowns. We can still use custom properties for styling because those will bleed through the web component. But if we want to do some serious theming and want to create flexible web components, the next one is probably even more interesting…

The ::part pseudo element

It allows you to style specific named parts of a web component from outside its Shadow DOM.

Here is an example of this. Let’s combine a few things; Imagine the following example:

<my-element>
    <h2 slot="header">Header Content</h2>
    <p slot="content">Main Content</p>
</my-element>

This is what our web component looks like (give extra attention to the attributes of the innerHTML):

class MyElement extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: "open" });
    shadow.innerHTML = `
	    <style>
            div {
            border: 3px solid blue;
            }
        </style>

        <div part="header"><slot name="header"></slot></div>
        <hr/>
        <div part="content"><slot name="content"></slot></div>
    `;
  }
}

customElements.define("my-element", MyElement);

Now the best way to explain this is to visualize it with borders. By default, the <div> element inside of our shadow DOM has a blue border. We want to give a green border to the <div> wrapping the header slot, we can access this:

/* style our default h2 */
h2 {
    margin-block: 30px;
    color: red;
    border: 3px solid red;
}

/* style our web component parts green border for header */
my-element::part(header) {
    border: 3px solid green;
}

Here is that in a little CodePen:

The developer experience of web components

I’ve shown you some basic examples and it’s time to be honest… Writing webcomponents like this isn’t a great developer experience… at least in my opinion. It seems to work fine for basic examples, but I just love a bit of structure and colors in my editor instead of just throwing every output into a string.

Inspired by Lucien Immink, a colleague of mine who has been touring with a presentation stating that a little library called “Lit” is on fire. I decided to give it a go… and yes! It was a lot more fun to write a little web component. Thank you Lucien for the hot tip.

Lit: Using a library to help create web components

The Lit library is designed to make it easier and more efficient to work with web components. It offers several advantages that can ease some of the burdens by just writing them the normal way.

Lit provides a more declarative syntax for defining components. Using this approach can make our code easier to understand and manage, as opposed to the more complex style needed when dealing directly with the Web Components API. It also has this neat templating system that combines HTML and JavaScript template literals, making it more convenient to work with dynamic content. This not only helps with some nice colors in our editor but also helps with their updating strategy that only updates the parts of the DOM that have changed. This can lead to improved performance compared to manual updates.

With all of these things combined, I’d say it’s worth taking a look at it if you’re serious about web components as this library is only about 6kb in size.

There is a lot more going on such as experimental server-side rendering, but I’m just starting with this and will need to get into the details a bit more myself.

To give an example, this is the same border demo, but then in Lit

Final thoughts and demo

I’ve just gotten into this whole web component thing and I really like what I see. I’m so late to the party and it has been on my list for a while. I love the web platform and because I keep a high interest in Open UI, I was inspired to take a deeper dive into this kind of thing.

I love the philosophy behind web components and believe that it’s an important thing to get into. As companies grow bigger, so does the need for different frameworks, which makes web components a perfect thing to integrate in your company’s ever evolving tech stack.

So many people are talking about them lately and I’m just giving it a go. I found the basics and lingo hard at first, so I hope that writing this article this could clear up some of the fog for others sharing the same struggle.

Here are a few reads that I enjoyed:

And also a special shoutout to the Frontend masters course on web components by Dave Rupert. Yes, it is a lesson you need to buy, but it helped me to get a start on the matter. In the next article, I will be doing a little breakdown of how I created my first (useless) web component.

Yes, it’s useless
Yes, It might not be the best example out there
Yes, it’s an npm package
Yes, I am so proud of it because “I did a thing”

You can find the for-love element with info here

And an example right here at the end of this article. Next article I will do a step by step of the creation of it.

 in  html , javascript