Before we begin talking about the trade-offs, let's look at the desired state, why someone should use Web Components and what benefits it provides. This standard allows you to create framework-independent UI components. Instead of re-inventing the same concept of UI component for every framework, there could be a universal solution using Web Components standard. They will also be more simple and lightweight, as the API is already built into browsers and you do not need to load additional runtime to your web page.
Web Components are defined as custom HTML elements where you can attach your custom behavior. You do not need to learn additional proprietary framework conventions, as you can think about using Web Component same way as you would use <button>
or <input>
.
Now let's check how these expectations match with the real state.
To use a Web Component on a page, you need to define it in custom elements registry: window.customElements.define('my-button', MyButton)
. MyButton
is a class with the component implementation and my-button
is a name which you should use in your HTML to render this component. The name has to be unique on the page, you cannot define two custom elements with the same name. You need to be careful when naming your components and to avoid possible conflicts with 3rd-party code.
We already encountered the same issue with CSS class names and the experience shows that naming conventions do not protect against all issues, in big enough codebases the naming conflicts are inevitable. Having globally unique naming requirement in Web Components makes it non-scalable and blocks its use in big projects (or projects that have ambitions to become big).
Some readers may point to some Web Components based libraries which work around this limitation by automatically generating unique names. However, it is important to remember what we called in the desired state. Web Components were meant to be "simple to use as built-in HTML elements". When we start applying workarounds, this argument does not work anymore.
In the future this could be solved with scoped custom element registries, however this proposal is at very early stages. Until then, it is better to avoid using Web Components in complex projects.
Often Web Components render their content using Shadow DOM. This allows to encapsulate the component implementation. Usually people refer to Shadow DOM as scoping for CSS-styles, but it also isolates other unexpected things. For example, consider this form:
<form>
<label for="name">Name</label>
<input id="name" >
<button type="submit">Send</button>
</form>
Here we have a label associated with an input and a button which submits the form. All of this works natively, without additional JavaScript.
Potentially we could replace the default controls with custom Web Components, for example using <custom-button>
which contains a styled version of <button>
inside. However, this replacement breaks the submission handler. Button inside Shadow DOM loses connection to the outer form and stops working. The same happens if we replace <label>
with <custom-label>
and <input>
with <custom-input>
respectively. Clicking inside <custom-label>
will not focus the connected input, because it is inside a different Shadow DOM instance and therefore not accessible.
There is an open request to provide an alternative API for connecting elements across Shadow DOM boundaries. However, even when this will be shipped, an additional JavaScript would be required, which does not match the expectation on "simple and lightweight" Web Components.
There are also difficulties with isolation of CSS-styles in Shadow DOM. It does isolate selectors, but does not isolate CSS-variables. You need to be careful when naming your CSS-variables, because they could be accidentally overridden, when someone defines --background-color: something
above in the DOM tree and you also use the same name but for a different use-case.
Shadow DOM does not reset relative unit sizes rem
and em
as someone could expect. You will need to use px
if you want to isolate your component scale from overrides.
Overall, it seems like Shadow DOM is not suitable for building isolated UI-widgets. There are things you would like to be isolated (styles), but they are stil some leaks. Same time there are also things you prefer to keep open, but they are isolated (element label associations).
For the reference, Salesforce Lighting Web Components are looking to provide an opt-out from Shadow DOM in their components. This RFC calls out some difficulties they found in Shadow DOM too.
Content Security Policy (CSP) allows you to restrict which origins are allowed to load resource into your page. It also disallows using inline styles in style="..."
attribute or <style>...</style>
tag. You can allow them by adding unsafe-inline
value into your CSP expression, but as the name suggests, this is not safe.
With Shadow DOM styles need to be loaded into each shadow root separately. Typically you would create a single CSS bundle with styles for all components. In Shadow DOM you need to create a separate stylesheet for each component. Rendering them as <link rel="stylesheet" href="...">
is not efficient as it produces more network requests and slows down the component rendering. Most Web Component libraries deal with it by embedding styles into the JS code and dynamically adding them to shadow roots via <style>...</style>
tag, which is not allowed in tight CSP configuration. This causes problems for users, for example there is an issue in Stencil project.
In the future, there will be construct stylesheets API which allows creating CSP-safe dynamic styles. But at the time of writing it was not ratified by other browser vendors than Chrome, so web developers have to rely on the workarounds to provide cross-browser support.
The list above is not exclusive. There are other concerns. Some of them are highlighted in Why I don't use web components article. There are also naming collisions with future HTMLElement API additions. I might update this post with more findings in the future.
There are some blockers for Web Components on their way to become the standard for building UI components. The blockers are in the web standard itself and cannot be fully solved on userland.
Even though I would like to be able to build more lightweight and simple components, Web Components are not the solution here. If we want to deliver great developer and end-user experience, we will have to look into the direction of framework-specific components.