Before I describe the two different paradigms for enhancing HTML tags, let’s first explain what it means to enhance an HTML tag.
I’ll use the <form> tag as an example, since I’ll be showcasing a couple of real-world form enhancements below.
So, let’s say you have a <form> element which has been instantiated as an object in your Document Object Model (DOM) as part of the HTML deserialization process (aka the markup is parsed, turned into DOM, and rendered to screen).
At the moment the form element is instantiated and connected to the document, you want to enhance the form by adding two features:
- You want to display some fancy visual validation feedback (e.g., red text below a blank field saying “Please enter your name”, etc.).
- You want to autosave form fields so if the browser session is interrupted, the user can load the form back up and continue from where they left off.
These enhancements are accomplished by executing JavaScript which sets up a reference to the form, adds event listeners, perhaps modifies the DOM to render some additional content, and exerts a degree of control over the lifecycle of the form.
The question then becomes, how do you set up this JavaScript? How does a wad of code get spun up in a 1:1 relationship with the element to be enhanced?
There is a bad way, and two good ways.
❌ First, the bad way:
<form id="some-form">
...
</form>
<script>
const someForm = document.querySelector("#some-form")
// do stuff with the form
</script>
(In the past, this would have likely been written using jQuery APIs, but the principle is the same.)
The reason this is bad is because you are brute-forcing the execution of the code in an imperative way, rather than using a declarative technique to execute the code whenever the relevant tag shows up. First of all, you likely want this code living in a tree of JS files somewhere else, rather than in a script tag next to the form. You might want the code to apply to various sorts of forms, not just one form with a particular ID. And why even use an ID at all (which is a fragile technique)? Plus this technique will completely break at any time you use reactivity or hypermedia: what if the form comes in via a fragment after the page has loaded? Will your code know that an ajax fragment has suddenly resulted in a DOM update?
The year is 2026…we have much better ways of accomplishing our goal!
Let’s look at the two good enhancement paradigms.
✅ I’ll pseudocode the first one, because there’s not yet a vanilla web API to make it work. Essentially, this paradigm is based on using a MutationObserver to look for a magic attribute on any element, and then inspecting that attribute in order to determine how to execute the relevant JS code to enhance the element. For example:
<form add-enhancements="validate autosave">
...
</form>
As soon as this form is instantiated, the observer will look for add-enhancements, parse the attribute into two terms: validate and autosave, and then based on a registry of available enhancements, run those two enhancements by passing along the form element so the enhancements can perform their tasks.
You may have previously used real-world solutions which operate on this principle, for example Hotwired Stimulus with its “controllers” concept. A web-native version of this would be the concept of custom attributes. They might work in a similar manner to custom elements, only instead of a 1:1 relationship between a custom HTML tag and JS element code running in a subclass of HTMLElement, there’d be a 1:n relationship: one tag could be enhanced by any number of bits of code via custom attribute lifecycles, and the tag itself would not be a custom subclass of HTMLElement but could be literally anything, even a standard form.
I think that would be a fantastic API, but I don’t believe there’s any immediate effort to implement it in browsers. Thus userland solutions will need to be used for these use cases.
✅ The second enhancement paradigm comes to us via that good ol’ web API we already know and love: web components.
Or perhaps more specifically, HTML Web Components as they’ve come to be called. These are custom elements which don’t really exist to serve as “components” in the design system UI sense of the word, but are there to enhance HTML which already exists.
Here are a couple of real-world form enhancements using this paradigm, which would be used in the markup like this:
<validation-enhancer>
<form-saver>
<form>
...
</form>
</form-saver>
</validation-enhancer>
This is a type of enhancement via element composition. By layering the form within one or more parent custom elements, you can modify the behavior of the form and add new features. This works in production today thanks to a couple of new libraries:
- validation-enhancer by Alistair Davidson
- form-saver by Aaron Gustafson
I think it’s fascinating that both of these projects launched just in the past few weeks, tackling totally different form problems but using the same paradigm of enhancement. While there are very specific cases where HTML web components aren’t a great solution (for example, you can’t insert custom elements inside of table markup, thus you’d have to wrap an entire table rather than any specific rows or columns), for the most part this is a tried-and-true way of authoring enhancements using vanilla web APIs.
I love seeing stuff like this! It would be awesome to see a lot more folks writing enhancements in this manner—at least until we do get a custom attributes API.
But…what about is=? Inevitably when this conversation comes up in discussions of web components’ capabilities, certain people will bring up the is= attribute and take a dump on Safari for nixing it. is= would let you write something like:
<form is="form-saver">
...
</form>
And then you could literally write a subclass of HTMLFormElement as a custom element which would get instantiated in this case.
Two major problems with this idea, and why I have always agreed with the WebKit team for nixing it:
- It creates a dangerous coupling between the implementation details of specific types of elements and their userland subclasses. Suddenly, you’re not just subclassing
HTMLElementwhich is an abstract superclass, you’re subclassing forms and buttons and video players and who knows what. There are a wide variety of headaches around future compatibility and breakage in such a scenario. - An even bigger issue in my opinion: You can only do this once! Duh.
In the above example, we might have used is="form-saver" to add autosave functionality to the form, but what if you also wanted the enhanced validation behavior? ❌ Too bad! 👎 You can’t add multiple terms to the is attribute because JavaScript doesn’t allow multiple inheritance. In other words, that form simply can’t be instantiated based on two or more class definitions. Just not how OOP works on the web or in JavaScript.
What we really need is the ability to enhance any existing HTML tag with an arbitrary number of enhancements. That’s why a proposed API like custom attributes, or today’s composition via multiple parent custom elements, are far preferable solutions than the is attribute and its limited direct inheritance.






















