The problem with web components

Web components are becoming increasingly popular within the web community. They offer a way to standardise and encapsulate JavaScript-enhanced components without a framework.

However, web components have a number of drawbacks. For instance, they have a number of technical limitations and are easy to misuse in a way that excludes users.

It’s possible—and certainly my hope—that web components will improve over time and these issues will be resolved. But for now, I’m holding fire on them.

In this article I’ll explain why that is, and suggest an alternative way to develop components in the meantime.

They’re constraining

In his criticism of web components, Michael Haufe explains that:

  • custom CSS pseudo selectors can’t be used with web components
  • they don’t work seamlessly with native elements and their associated APIs
  • if we wanted to create a custom button, for example, we can’t extend the HTMLButtonElement directly, we have to extend the HTMLElement

Additionally, web components have to be defined with ES2015 classes which means they can’t be transpiled to give more people the enhanced experience.

So, straight off the bat, there are a number of technical constraints to work around when it comes to using web components.

They’re not widely supported

Currently, web components have relatively poor cross-browser support, so the enhanced experienced won’t work for everyone.

Web component support on caniuse.com

That doesn’t mean we can’t use them, it just means we’ll need to provide a baseline experience that works for everyone else. That’s progressive enhancement.

But we should think seriously about whether the choice to use web components is the most accessible option. If we don’t use web components, we can provide the same rich experience to a significantly wider group of people. I’ll explain how later.

Polyfills offer a way to provide broader support. But they are slow, unreliable and hard to work with in general, and have a number of specific limitations when used to make web components work more broadly.

So while it may be preferable for us as code authors to use standards-based technologies, it’s not necessarily beneficial to our users—which should always be our first priority.

They’re easily misunderstood and misused

Jeff Atwood said that any application that can be written in JavaScript, will eventually be written in JavaScript.

But just because we can use JavaScript to do something, doesn’t mean we should. There’s even a W3 principle that says we should use the least powerful tool for the job.

Web components are made up of JavaScript APIs which means we should use them only when we need JavaScript. But as Jeff Atwood predicted, people sometimes use web components when they don’t need to.

When we make JavaScript a dependency and don’t provide a fallback, users get a broken experience. Even webcomponents.org, built using web components, shows a blank web page when JavaScript isn’t available.

Completely broken experience on webcomponents.org when experiencing a JavaScript failure

By the same token, it can encourage people to make components that request their data with AJAX and render themselves, like little iframes.

This type of approach causes a number of avoidable issues which I’ll explain by way of an example.

Imagine we want to load a table showing the sales figures for a product our website sells using AJAX like this:

<sales-figures></sales-figures>

Firstly, it’s just a table. There’s no column sorting and therefore no need for JavaScript. Browsers provide the <table> element for this exact purpose and it works everywhere.

Secondly, as mentioned above, when a browser doesn’t support web components, or JavaScript fails to run, users won’t see anything.

To make our table work in these situations, we would need to put a <table> inside <sales-figures>. This is known as graceful degradation.

<sales-figures>
  <table>...</table>
</sales-figures>

If the component already has a populated table on the page when the page loads, wrapping <sales-figures> around it gives us and our users nothing.

Finally, using AJAX can introduce a number of usability and accessibility issues.

  1. AJAX is often slower than a page refresh, not faster.
  2. We’ll need to create custom loading indicators, which are usually inaccurate and unfamiliar to users, unlike browsers’ loading indicators.
  3. We’ll need to make AJAX work cross-domain, which isn’t straightforward.
  4. As the components load the page will jump around causing visual glitches and potentially making users click the wrong thing. You may have heard about skeleton interfaces as a way to solve this problem. They are placeholders put where the components will end up being shown once loaded. But while they help a bit, they don’t fully solve the problem because they can’t always predict the exact size of the content that will load.
  5. Point 4 affects screen reader users too because they won’t know whether the components have loaded, have failed to load or are in the process of loading. ARIA live regions provide a way to communicate these states to screen readers. But when several components are being loaded, the user will be bombarded with announcements.

Scale this up to several web components on a screen and we risk giving users a very unpleasant, exclusive and slow experience to contend with.

Components that depend on AJAX requests to the server are no longer framework agnostic and therefore interoperable. This somewhat defeats the object of using web components, given that interoperability and technology agnosticism are 2 of the main benefits they aim to provide.

Importantly, none of these problems are the fault of web components per se. We could easily develop components to work like this without web components. But, as demonstrated, it’s easy to misinterpret web components and unknowingly use them in a way that hurts both users and code authors.

They’re hard to compose

Let’s say we have just 2 web components. One for sortable tables and another for expandable rows.

<sortable-table>
  <table>...</table>
</sortable-table>

<expandable-rows>
  <table>...</table>
</expandable-rows>

But if we want a sortable table with expandable rows then we need to nest the components like this:

<expandable-rows>
  <sortable-table>
    <table>...</table>
  </sortable-table>
</expandable-rows>

The relationship between <expandable-rows> and <table> is unclear. For example, it’s hard to tell whether <expandable-rows> is operating on the <table> or the <sortable-table>.

The order matters, too. If each component enhances the table it could create a conflict. Also, it’s not clear which component initialises first—the inside one or the outside one.

(Note: you may have heard about the is attribute as a way around this but Jeremy Keith explains that browsers aren’t going to implement this in extensible web components.)

They can’t just be dropped into an application

One of the supposed benefits of web components is that we can drop one script per component onto the page and they just work—regardless of the application or tech stack.

But unlike standard elements, we may need to add additional code to get them to work properly. In some ways this is a bit like adding a framework or library.

One example of this is polyfills which I mentioned earlier. If you choose to use a polyfill to provide broader support, then that code needs to be ready and waiting in your web page.

Another example would be when you need to stop JavaScript-enhanced components from making the page judder while initialising.

This is usually fixed by adding a script in the <head> of your document to provide a hook for CSS. This in turn is used to style the component based on JavaScript being available and avoids the page judder.

This is perhaps of little consequence overall, but it does considerably negate one of the supposed benefits of using web components.

Framework agnostic components without web components

You may have heard web components being sold as an alternative to using frameworks.

While I’m in favour of creating interfaces without client-side frameworks, this is misleading for a number of reasons.

Firstly, client-side frameworks usually provide additional features besides enhancing pieces of the interface.

Secondly, web components can be used in tandem with frameworks.

Lastly, we’ve been able to create JavaScript-enhanced components without frameworks and web components for a very long time.

By creating components like this we can avoid the drawbacks I’ve described in this article.

Let’s use the same sortable table and row expander to do this.

Firstly, we need to create a JavaScript file for each component—the same as if we were using web components. We can define the SortableTable and RowExpander classes inside.

SortableTable.js // define SortableTable class and behaviour
RowExpander.js // define RowExpander class and behaviour

Once that’s done, we can initialise the components like this:

// grab table
var table = document.querySelector('table');

// initialise sortable table
var sortable = new SortableTable(table);

// initialise row expander
var expander = new RowExpander(table);

We can make these components fire events just like web components. Something like this:

sortable.addEventListener(‘sort’, fn);
expander.addEventListener(‘expand’, fn);

By using regular JavaScript in this way, not only can we write clean code, free from technical constraints, but we get to give that code to a significantly wider user base.

In conclusion

Web components hold a lot of promise because they give code authors a way to create interoperable components based on standards.

As a result, it should be easier to understand other people’s code and create components that can be reused across projects.

But even if we choose to provide enhancements exclusively for cutting edge browsers that support them, there’s still several limitations and issues we need to tackle.

My hope is that web components get better in future. But until then, I’m sticking with regular JavaScript to avoid the current technical limitations and provide the most equitable experience to users.

Thanks to Amy for editing this article. 🙌