Web components in 2021: the Good, the Bad and the Ugly
Tags: webdev, webcomponents, javascript, html
Published: 2021-05-02 16:42:47

[Web components](https://developer.mozilla.org/en-US/docs/Web/Web_Components) are a native set of features that provides outstanding scoping of styles and functionalities. They can be used in a regular, framework-free web page, but also with any Javascript framework of your choice (e.g. React, Vue, Angular, Svelte, etc.). This makes web components excellent for building reusable elements that need to be shared publicly or reused across multiple projets. At least, in theory.

In reality, there are some drawbacks that can make web components almost unusable in some projects. 

In this article, I will explain what makes web components great, what their drawbacks are, and I will provide some guidance to help you decide whether or not you should use them in your projects.

---

## The Good

The two main features that make web components powerful are the [Custom Elements API](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements), and the [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM).

The Custom Elements API is what allows you to create and register your components as new HTML elements. It also allows you to define lifecycle callbacks for your new element. Overall, it is pretty great and fairly simple to understand and get into, for both novice and experienced developers alike.

The Shadow DOM is what provides all of the encapsulation of styles. It gives your components their own DOM, which is separate from the rest of your document. This means that global styles cannot affect it (except for CSS custom properties / variables), and that its own styles cannot affect other elements in the parent document. 

The HTML `<template>` and `<slot>` elements are also used in most custom elements, allowing you to easily create templates with dynamic content without having to reach for a third-party templating system or language.

Browser support for all of these features is great: unless you're still supporting Internet Explorer, you're unlikely to run into any deal-breakers. There is one exception to this, which will be explained later in "The Bad" section.

Plus, as mentioned at the start of the article, not only are web components compatible with just about every Javascript framework out there, but they can also be used in good old vanilla Javascript, without a framework. That's because web components are basically just ES6 classes extending the native HTMLElement. That means you can share components across your project or your company's entire ecosystem.

Additionally, there are some [great libraries and packages to make building web components easier](https://www.webcomponents.org/introduction#libraries-for-building-web-components), as well as an online platform where you can find and share web components with others: [webcomponents.org](https://www.webcomponents.org/).

---

## The Bad

### Flash of Unstyled Content
Let's start with the Custom Elements API. The only drawback I have experienced with custom elements is the potential for a Flash of Unstyled Content. Since custom elements are declared and registered in Javascript, it can take a few milliseconds for them to be loaded, processed, registered, and finally rendered. While this is happening, your custom element are left unstyled or hidden.

This could be a major drawback for a marketing website where you have just a few seconds to engage with your visitors in order to keep their attention, but in web applications, it's not really a deal-breaker, especially since your browser's cache dramatically dampens the issue after the initial loading.

Here's an example of FOUC with a "tabbed container" web component on a reload without cache (on a local development server):
![Web component flashing unstyled content for a few milliseconds before rendering correctly](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n7k4mxzjahpuz0rjf3gp.gif)

Here is the same component rendering on reload, with browser cache (still on a local development server):
![Web component loading immediately without any visual glitches or FOUC](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vjd21un6mf1lbnjz4ay5.gif)

As you can see, browser cache makes this a non-issue for repeat visits.


### Shadow DOM doesn't play well with native forms

The biggest issue I have encountered with web components is the fact that they do not play well at all with native form functionalities. This is due to two things:

1. Custom elements cannot extend elements other than `HTMLElement` _(without tedious workarounds and major drawbacks)_;
2. Form elements inside the Shadow DOM are not considered as such by the component's parent form.

Remember how the Shadow DOM doesn't use global styles? This means that if you want to use a `<form>` inside a web component, you will have to re-define the styles of every `<input>`, `<select>`, `<textarea>`, `<button>`, `<label>`, `<fieldset>`, and more, within your component's styles.

Of course, you could make each of these elements their own web component, so they each encapsulate their own styles. However, because form elements such as `HTMLInputElement` cannot be extended by custom elements, your custom input component has to include the `<input>` in its Shadow DOM. And this is where you run into the next issue: inputs (and other form elements) within the Shadow DOM are not considered part of the form.

For example, if a form's submit button is inside the Shadow DOM, the form cannot be submitted by pressing Enter inside an input anymore, unless you add your own `keydown` event listener to replicate this feature yourself. 

Here is another example that is a little more complex and telling. If you want to make a custom input, you have three solutions:

- You can generate a `<input type="hidden">` in the regular DOM, beside your custom element, and manually replicate a bunch of built-in features to ensure your input is synchronized correctly at all times, triggers the right events, is validated correctly, is accessible, looks good, and works well.
- You can make every form element, including the `<form>` itself, its own web component, and forego native `<form>` elements for your entire project.
- Handle every form who uses this custom input element with Javascript

If you are already in a Javascript-heavy environment, where every single form is handled via Javascript, and every component implementation already requires a lot of work in order to be usable and accessible, this might not seem like a major issue.

However, if you are more vanilla-oriented, newer to web development, or simply like simple solutions and environments, this is likely to be a MAJOR deal-breaker.

A non-negligible percentage of the web components I would like to build are meant to work with forms or form elements in one way or an other, and I expect it is the same for most other developers. 

---

## The Ugly

The worst part is that there isn't much information on the web about what is being done to fix or circumvent this issue of incompatibility with native forms. 

Web component libraries like [Shoelace](https://shoelace.style/) simply implement their own custom form element, which must be handled manually in Javascript.

Librairies that aim to help build web components, such as Google's [Lit](https://lit.dev), cannot allow extending built-in elements [because Safari doesn't support customization of built-ins](https://github.com/lit/lit-element/issues/879#issuecomment-575236461).

---

## Where we stand, and whether should you use them

Overall, just a few weeks/months after embarking on my web component journey with a big smile and sparkling eyes, I find myself not pessimistic, but slightly disappointed about the current state of web components and their future outside of Javascript framework projects and ecosystems.

I still believe the idea and general implementation of web components is great. But the drawbacks when it comes to native forms make them much less easy to learn and to implement into. 

### You should use web components...

- if you're already handling all your forms manually in Javascript
- if you have (or plan on having) multiple projects or ecosystems with different technology stacks in which you need to share/reuse components
- if you don't mind taking a lot of time to reimplement built-in functionalities and accessibility before you can really start working on your own business-related features (or if you can use an existing component library like [Shoelace](https://shoelace.style/) to save on the initial development time and costs)
- ... or if you don't need your components to interact with forms or form elements

### You should not use web components...

- if you want to retain the ability to use native forms 
- if you need to support legacy browsers

---


## A light in the distance

Just after I initially published this article, @westbrook commented to let me know about the [ElementInternals spec](https://html.spec.whatwg.org/multipage/custom-elements.html#the-elementinternals-interface) which is currently implemented in Google Chrome (not in Safari or Firefox yet however). Once available in every browser, this could be a valid solution to the form-related problems I mentioned in the article. 

Check out the following articles to learn more about this spec, its implementations, and the available polyfills: 
- [More capable form controls (web.dev)](https://web.dev/more-capable-form-controls/).
- [Creating Custom Form Controls with ElementInternals (CSS-Tricks)](https://css-tricks.com/creating-custom-form-controls-with-elementinternals/)

---


## One last thing...

If you are not in a Javascript-heavy environment but would still like to use web components for your forms (e.g.: you're building a Laravel or Symfony web app), you always have to possibility of developing a universal form handler to overcome the problems that are described in this article.

Sure, it's more complicated than just using native forms, and it will require you to do some more development and testing before you can get started, but it's probably the simplest workaround.

If you do have any other workaround or solutions in mind, I'd love to see them here in the comments or on Twitter.