Web components are awesome. In fact, I wouldn’t hesitate to call them one of the coolest, most interesting features added to the web standards in a very long time. And yet, they’re generally overlooked, underused, and ignored by the majority of web developers. So what makes them so great in the first place, and why don’t they get the attention I feel they deserve?
The First Mistake
…was calling them web components.
Okay, that’s probably a little overdramatic. But there’s a very specific reason I chose to phrase it that way - because the word “components” already has a meaning in the realm of web development, and the two concepts are actually largely unrelated, despite popular opinion to the contrary. This linguistic flub, in my opinion, has caused a huge part of the the difficulties web components have had with getting off the ground in the web development space.
A component, in the traditional web sense, is a reusable block of composable code used by frameworks like React or Vue. They usually consist primarily of JavaScript, with only a very thin layer of HTML and CSS to provide their structure and are usually compiled away on the server side before they ever reach the DOM, meaning your page structure can change wildly between your original component-based code and the HTML it generates. This has its benefits, but it’s effectively a purely organizational concept - structuring your code into components encourages you to write more modular code, and building your entire page piece by piece from those components leads to a more consist overall consumer experience.
Web components, on the other hand - or custom elements, as they should properly be called - are new HTML elements with custom behavior attached. Rather than replacing your HTML by reorganizing it into chunks, custom elements work alongside your existing HTML to encapsulate and simplify localized behaviors. This allows them to be dropped trivially into any page without wildly restructuring your site around them, since once they’ve been defined, they act exactly like normal HTML elements - you just get to define the special behavior they represent.
The Second Mistake
… was how they’re defined.
As awesome as web components are, actually writing them by hand is a gigantic chore. Their JavaScript API is clunky, esoteric, and hard to understand for devs that are new to using web components. Take a look at this mess:
<script>
customElements.define('tag-name', class extends HTMLElement {
static observedAttributes = ['some-attribute']
#root
constructor() {
super()
this.#root = this.attachShadow({ mode: 'closed' })
}
connectedCallback() {
let template = document.querySelector('template#tag-name')
this.#root.append(template.content.cloneNode(true))
}
attributeChangedCallback(name, oldValue, newValue) {
console.log(oldValue, newValue)
}
})
</script>
<template id="tag-name">
<p>Hello, <slot>world</slot>!</p>
</template>
<tag-name some-attribute="value">Web Components</tag-name>
Everything here is clunky, awkward, and a little confusing. But, with a little elbow grease, we can make libraries that simplify this process. I’d personally recommend my little work-in-progress library, Facet, which allows web components to be defined directly in HTML itself - for example, the equivalent of the above component would be something like this:
<template component="tag-name" observe="some-attribute">
<p>Hello, <slot>world</slot>!</p>
<script on="attributeChanged">
console.log(event.detail.oldValue, event.detail.newValue)
</script>
</template>
<tag-name some-attribute="value">Web Components</tag-name>
Facet takes care of all the grody details for you, and lets you focus on just your component’s HTML and behavior. I highly recommend giving it a look if you’re interested in working with web components!
What’s the deal with shadow DOM?
Possibly the single biggest victim of the mis-marketing of web components is the idea of “shadow DOM”, a special section of your page hidden away inside a component that handles all the nasty internal details. It’s often portrayed as a convenient way to limit the scope of your CSS and keep nasty external JavaScript from getting to your precious elements, but what is it actually meant for?
I personally prefer to view shadow DOM as a way of creating your own replaced elements. For those not in the know, certain HTML elements like form inputs aren’t actually defined by your browser, but rather are replaced with OS-specific code at runtime. This can create some annoyances when it comes to styling them with CSS, but it allows them to behave consistently with your operating system’s built-in form inputs.
Shadow DOM functions in a similar way when used by web components - the little chunk of HTML inside the component’s shadow root is your “OS-specific code”, which the component squirrels away in shadow DOM so that the code using the component doesn’t have to think about it. Tools like the <slot>
element allow your web components to pull child elements inside the flow of their shadow DOM without disturbing their place in the “real” DOM, and all the pesky details of what makes your component tick are hidden in a convenient little black box.
In my opinion, ideas like declarative shadow DOM misuse this excellent tool, and yet because of this misunderstanding of when and why you should use shadow DOM, they continue gaining further traction.
Attribute Overload
As a result of the confusion between framework components and web components, one of the most common mistakes by people developing web components is to try to design them like framework components. Take a look at this simple example component in use:
<hello-world greetee="Web Components"></hello-world>
This doesn’t seem that egregious on the surface. Framework components are often designed similarly, with custom attributes that allow adjusting the component’s options and content. But there’s a conceptual issue with doing things this way - why are we putting content in a component’s attributes? That’s completely backwards to how HTML is meant to work.
<hello-world>Web Components</hello-world>
Ahhh, that’s more like it. HTML is a hierarchical structure to start with, let’s make use of that hierarchy! This example isn’t that big of a difference in the end, but when you start dealing with more complicated components, the difference becomes night and day. Attributes have plenty of great uses, of course - they’re just not meant for content, and yet many frameworks directly encourage that type of structure.
JavaScript vs. HTML
The difference between framework components and web components really does come down to this in the end. In the red corner, we have the reigning heavyweight champion JavaScript, known for its power but infamous for dragging things out with verbose code and too many dependencies. In the blue corner, we have the former champion HTML, coming back out of retirement to reclaim its title. Who should we be rooting for in this fight?
You might already be able to guess where I stand, but I will always root for HTML as the #1 language of the web. While the consistency JavaScript provides to developers by being able to run the same language on both the server and the client is nice, when it comes to designing a UI it just can’t compete with a language specifically designed for creating web UIs. As awesome as JavaScript is, on the client side it should be relegated to its original intended place - supplementing HTML and CSS by allowing developers to define more complex behaviors, rather than supplanting them as your site’s core UI language.
Web components, perhaps surprisingly, fit perfectly with this goal. A relatively self-contained piece of JavaScript defines their behavior, and then they’re used just like normal HTML, rather than your site’s frontend being written entirely in JavaScript and then using JSX or a similar rendering library to embed HTML into your JavaScript where necessary. They offer the reusability of framework components with the live abstraction of custom replaced elements, without fighting the HTML-first design that the web was created with from the start.
So what’s the point?
That’s a valid question. What really is the point in making a distinction between a reusable chunk of code and a new HTML element?
The way that framework components encourage you to organize your code is to use components pervasively throughout your entire application. Everything either is a component, or is part of how a component functions. In a very real sense, you could call it COP, “component-oriented programming”. There’s nothing inherently wrong with this approach, of course - but it’s a vast departure from how the web was initially designed to function, and fights against the flow rather than with it.
In contrast, web components are best used sparingly alongside normal HTML code. They act as new HTML elements that provide specific, localized functionality, without expecting you to rethink your entire site’s structure. Unlike framework components, they work within the existing flow of parent and child elements rather than overriding it, and they’re also framework-agnostic, View Source-compatible, and perfectly AJAX-friendly with no strings attached.
Web components definitely aren’t for everyone. But I do think that when used well, they can revolutionize how we think about developing for the web, by encouraging us to work with the way the web is designed rather than against it, while still giving us the extensibility and reusability that framework components are designed for.
Like what you read today? Consider checking out my Github and Ko-fi, or support me on Substack using the link below: