Web Components Are Easy, Mostly

🕒


Web components are a tool you can use to give layout and functionality to custom elements, e.g. <hello-world>. There are, roughly, three types of web components. Most web component frameworks are using Imperative Shadow DOM web components.

Let me break that down. Imperative means you create and modify elements through function calls instead of declaring a layout (e.g. in HTML). DOM is the tree model of the layout (HTML). A shadow DOM is a separate tree, which means it is somewhat encapsulated from the rest of the document (the Light DOM.)

a diagram demonstating the infrastructure of this website, compared to the previous
The three types of Web component.
(CSR is client-side rendered, SSR is server-side rendered.)

Making Imperative Shadow DOM Components

You define a component in JavaScript by making a class and then adding your component to a registry. Let's try and recreate this element in a web component:

drawing of a deer, talking to you.

Hello There! This is a simple component that wraps around a provided element.

class SpeechBox extends HTMLElement {
  connectedCallback() {
    const shadow = this.attachShadow({ mode: "open" });
    
    const wrapper = document.createElement("div");
    wrapper.style.display = "flex";

    const speechImage = document.createElement("img");
    speechImage.src = "https://j0.lol/static/speech/deer/neutral.png";
    speechImage.setAttribute("height", "120px");
    
    const speechBoxBorder = document.createElement("div");
    speechBoxBorder.style.border = "5px solid rgb(176 152 232)";
    speechBoxBorder.style.background = "black";
    speechBoxBorder.style.color = "white";
    
    const slot = document.createElement("slot");
    speechBoxBorder.appendChild(slot);

    wrapper.appendChild(speechImage);
    wrapper.appendChild(speechBoxBorder);
    
    shadow.appendChild(wrapper);
  }
}

customElements.define("speech-box", SpeechBox);
Codepen sample

When the component gets loaded, connectedCallback() is called. This lets you do a lot, but here we instantiate a Shadow DOM 'root' node, and imperatively add nodes to it. The <slot> element will let you poke a hole in your Shadow DOM to include Light DOM elements.

drawing of a sad or worried deer, talking to you.

That connectedCallback() is pretty ugly! Nobody wants to write HTML like this. Or CSS! Surely there's a nicer way to do this, right?

Yeah. I wanted to demonstrate taking 'imperative' to it's logical conclusion. This is what libraries like Lit help with. Here's what a very simple component looks like in Lit:

Lit Playground

Being able to declaratively define your CSS and HTML is a lot nicer, and a lot of people who try to go down this route just re-invent Lit anyway.

Making Imperative Light DOM Components

This classification is more of a 'trick' more than anything. But it's a real thing you can do! You might prefer to work in the Light DOM if, for example, you are making components for your own website, and don't need encapsulation of styling. One thing to note is: because you lose the Shadow DOM, you lose the functionality of <slot> (and its sister <template>), which means you need to manually move around things you want slotted. Not much of a loss, though!


class SpeechBox extends HTMLElement {
  connectedCallback() {
    // get children before appending anything to element
    const previousChildren = this.children;
    
    const wrapper = document.createElement("div");
    wrapper.style.display = "flex";

    const speechImage = document.createElement("img");
    speechImage.src = "https://j0.lol/static/speech/deer/neutral.png";
    speechImage.setAttribute("height", "120px");
    
    let speechBoxBorder = document.createElement("div");
    speechBoxBorder.style.border = "5px solid rgb(176 152 232)";
    speechBoxBorder.style.background = "black";
    speechBoxBorder.style.color = "white";
    
    // slot children in here
    speechBoxBorder.replaceChildren(...previousChildren);

    wrapper.appendChild(speechImage);
    wrapper.appendChild(speechBoxBorder);
    
    this.appendChild(wrapper);
  }
}

customElements.define("speech-box", SpeechBox);
Codepen sample

Please note that you don't have to do this, and a simple custom element without any Javascript like this could still be called an "(Imperative) Light DOM" component.

Making Declarative Shadow DOM Components

So "Declarative Shadow DOM" is a method of using <template> (with a shadowrootmode attribute) to define a shadow DOM root in HTML only. You still need Javascript for initializing your custom element, and adding interactivity, but you can define styles and layout in HTML. You can make one like this:

Codepen sample

Compared to imperative examples, this is a lot simpler to understand. You make a template, punch a "slot" hole in it, and it just works. I like the idea of this a lot. You can just spit this out with PHP, or whatever you use as your server backend. The issue is, every time you want to use one of these components, you need to include the template again. This makes it quite a pain to use in practice, unless you want to do something like this:

(Did you know that you can use <?= and ?> here instead to omit the echo?)

You might think:

drawing of you, smiling.

Why didn't they just let you name and reuse <template> blocks, so you don't have to repeat yourself constantly?

It turns out that the people who proposed this had thought of this. Their answer is that you can just copy <template> blocks with Javascript to repeat blocks of HTML. That's nice, but it does make this use case a bit more painful. Of course, something like this can easily be abstracted over with a framework.

There are a few ways that this could be improved in the future. One proposal, Declarative Custom Elements, would allow you to define an entire element in HTML so you could use it later without being encumbered with the template.

Conclusion

I hope this was an okay introduction to Web Components! If you are just starting out using them, I think it's important to know what you are doing with them. Mainly just so you can pick the best method of using them. If you think I didn't give a good overview (or just want more to read), please read through some of the provided articles below. Thanks!

Further Reading