Web Components as Progressive Enhancement

Written by Paul Hebert on

A vanilla ice cream sunday with chocolate sauce, sprinkles, and a cherry on top.

Web components are a powerful tool for creating custom HTML elements, but you can run into challenges if you try to replace existing elements with web components. To get the best of both worlds, try wrapping existing elements in web components instead.

I learned this the hard way… On a few recent projects we’ve wanted to create an auto-expanding textarea: When a user types its height would increase so that its content is never clipped.

I was tired of rewriting this behavior across projects and frameworks and began thinking through how it could be written as a reusable web component. My first draft of an API looked like this:

<elastic-textarea name="textarea-name" id="textarea-id" rows="4">
  Here is my textarea content
</elastic-textarea>

This is pretty sweet. Developers can use this exactly like the textarea they’re already used to! But, as I was testing this out I noticed a couple of major downsides:

  1. Web components require JavaScript to render 1. This means that until the JavaScript is downloaded, parsed, and run, no textarea is displayed. Instead “Here is my textarea content” is displayed as an unstyled string. If a user has JavaScript disabled, or the JavaScript fails to load, the textarea just won’t work.
  2. The textarea element has its own complex behavior, APIs, and accessibility behaviors. I’d need to recreate a lot of this from scratch for my component. I’d need to apply the name, id, and rows to the actual textarea element that my JavaScript renders. I’d also need to add all of the textarea JavaScript APIs to my custom element (.value, .is-valid, etc.) 2

The Solution

We can avoid both of these drawbacks by wrapping and enhancing an existing element instead of replacing it. Here’s what that looks like:

<elastic-textarea>
  <textarea name="textarea-name" id="textarea-id" rows="4">
    Here is my textarea content
  </textarea>
</elastic-textarea>

This is a little more verbose, but it gets me the best of both worlds. Before JavaScript loads I have a fully functioning textarea. After JavaScript loads my textarea is progressively enhanced with additional functionality.

It could get a little tedious to wrap every textarea on the page. To avoid this we could set the component up so it can wrap multiple textareas and enhance all of them at once. This would allow you to wrap a whole form or page to enable this behavior:

<elastic-textarea>
  <textarea name="textarea-name" id="textarea-id" rows="2">
    Here is my textarea content
  </textarea>

  <textarea name="textarea-2" id="textarea-id-2" rows="5">
    Here is another textarea
  </textarea>
</elastic-textarea>

You can play with the finished component below:





We released this as an open source component you can use in your projects. Most of the JavaScript logic was borrowed from an excellent implementation my colleague Scott Vandehey wrote for a previous project.

The Sky’s the Limit

The elastic-textarea component is a single example of how web components can be used for progressive enhancement, but there are tons of other potential use cases for this technique. Here are a couple other examples to check out for inspiration:

Sharing is Caring

I’m a big fan of this strategy and I’m excited to create and share more progressively enhanced components. At Cloud Four we work on a lot of different projects for a lot of different clients, and I’ve found myself rewriting the same functionality across several projects. Next time I catch myself rewriting the same component logic for the third or fourth time I’m going to make it a web component so I don’t have to write it a fifth time.

By packaging these chunks of interactive logic as custom elements, we can make them easy to share and reuse across projects and frameworks. By enhancing native HTML instead of replacing it, we can provide a solid baseline experience, and add progressive enhancement as the cherry on top.


  1. There are various libraries that allow server side rendering of web components, but they usually require you to buy into a specific web component framework, and/or modify your build steps. These are useful tools for individuals projects, but don’t work as well for creating easily shareable components. 
  2. There’s a spec to allow extending existing elements but it looks unlikely that Safari will adopt it. On some projects, recreating element APIs can have a big payoff but it’s a lot of work if you’re just trying to add a little functionality to an existing element. 
Paul Hebert

Paul Hebert is a hybrid designer and developer at Cloud Four. When he's not designing and developing websites he enjoys bouldering, drawing, cooking, gardening, and eating too much cheese.

Never miss an article!

Get Weekly Digests


Comments

Add a comment

In an ideal world we’d be able to do something like <textarea name="textarea-name" id="textarea-id" rows="4" is="elastic-textarea"> to literally provide enhancement to the existing textarea element with web components, but WebKit disagrees with that part of the specification and has refused to implement it.

Replies

Yeah I wish that WebKit would adopt that part of the spec. It would unlock a ton of possibilities and make it easier to implement this type of functionality.

Almost the same I was going to comment, but glad to find it was better expressed already 😀

You should have used textarea is=”elastic-textarea” instead. Yes, apple doesn’t support that, but there is a little polyfill to fix that, and it’s a better approach (you are effectively extending the textarea class).

Replies

That’s a great option as well! Since one of my main goals was reuse and sharing between projects I was hesitant to require or bundle the polyfill but in a lot of scenarios that approach works well.

We use StencilJS and create hidden inputs in our web components to maintain the functionality and robustness of the original elements. A big source of inspiration for us has been Ionic (who created StencilJS) and Calcite who also went all in with StencilJS.

It’s not perfect, but it does help if you need your web components to be usable in other frameworks like React because it can create bindings essentially turning the new web components into React components or what have you.

It also helps create the web components faster because it provides extra APIs like virtual DOM, JSX, and async rendering.

Replies

That sounds like a good approach as well! We’ve used StencilJS on previous projects and had really good results. The framework binding’s are super powerful and flexible.

My colleague Tyler wrote a great article about our experience using a similar approach.

At the end of the day both approaches can work well and the best one to choose depends on the project’s priorities and constraints.

I love the idea of progressive enhancement via web components. I wrote <web-share-wrapper> in order to use this approach for the Web Share API, wrapping other share buttons that could then be replaced by a single Web Share enabled button where supported. I’d love to see more of these in the wild.

Leave a Comment

Please be kind, courteous and constructive. You may use simple HTML or Markdown in your comments. All fields are required.


Let’s discuss your project! Email Us