title: Web Components as Progressive Enhancement
url: https://cloudfour.com/thinks/web-components-as-progressive-enhancement/
hash_url: 72b4d15d28
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:
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.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.) 2We 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 textarea
s 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:
aria-label="Permalink for The Sky’s the Limit"
class="HashHeading-link">
<svg viewBox="0 0 24 24" width="24" height="24" class="Icon" role="presentation"><g fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><path d="M13.77,10.09l-0.71-.71a4,4,0,0,0-5.65,0L3.16,13.63a4,4,0,0,0,0,5.66l1.4,1.4a4,4,0,0,0,5.67,0l1.41-1.41"/><path d="M10.23,13.62l0.71,0.71a4,4,0,0,0,5.65,0l4.25-4.25a4,4,0,0,0,0-5.66L19.43,3a4,4,0,0,0-5.67,0L12.35,4.43"/></g></svg>
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:
details-utils
component that wraps and progressively enhances the details
element. (This was a big source of inspiration for my approach here.)aria-label="Permalink for Sharing is Caring"
class="HashHeading-link">
<svg viewBox="0 0 24 24" width="24" height="24" class="Icon" role="presentation"><g fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><path d="M13.77,10.09l-0.71-.71a4,4,0,0,0-5.65,0L3.16,13.63a4,4,0,0,0,0,5.66l1.4,1.4a4,4,0,0,0,5.67,0l1.41-1.41"/><path d="M10.23,13.62l0.71,0.71a4,4,0,0,0,5.65,0l4.25-4.25a4,4,0,0,0,0-5.66L19.43,3a4,4,0,0,0-5.67,0L12.35,4.43"/></g></svg>
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.