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.)
(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:
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);
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.
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:
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);
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:
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:
You might think:
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.
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
- Web Components (MDN)
- Web Components Are Easier Than You Think (CSS-Tricks)
- Imperative and Declarative Shadow DOM
- Web Components: Working With Shadow DOM (Smashing Magazine)
- Declarative Shadow Dom (Web.dev, Google Chrome team)
- Imperative Light DOM Web Components
- Light-DOM-Only Web Components are Sweet (Frontend Masters)
- You can use Web Components without the shadow DOM (Harris Lapiroff)
- Miscellaneous
- Creating Custom Form Controls with ElementInternals (CSS-Tricks)
- Styling a Web Component (CSS-Tricks)