Best Practice for Creating Custom Elements

Warning This article was written over six months ago, and may contain outdated information.

It looks like custom elements, and web components in general, are beginning to break through into general developer consciousness, as I see more and more articles and talks discussing what they are, what they are good for, and how to make them.

As they’re not yet being used heavily in development, however, I think there’s a good opportunity to define best practices in the way we use them. In this post I want to propose a best practice method for writing custom elements: I’ll do that by comparing two methods for creating custom elements, along with the advantages and drawbacks of each.

Aside: it strikes me that I haven’t written about custom elements here on my own blog, despite having given a few talks and written a few published articles on the subject. In case you’re not sure what they are, I recommend you read my Detailed Introduction To Custom Elements first.

Important Note: Please be sure to read the Future of Custom Elements section at the end of this post for some critical news about the status of custom elements.

A New Custom Element

The first way to create a custom element is from scratch, registering a completely new element with the DOM. In this example I’m registering, then implementing, a new element which I will use as a button†. I’m calling my element new‐button, as I am a man of limited imagination:

I think this has two major drawbacks. First, should JS not be working in the user’s browser, this ‘button’ will do nothing; all that the user will see is the text inside the element. This will include not just cases where JS has failed or been disabled, but also where it hasn’t yet loaded (big JS file, slow network connection).

Second, and more onerously for me, this new element would use the standard HTMLElement API interface, so to make it act as a button I would have to manually add all the unique properties and methods of a button: all of the form properties, accessibility features, behaviour indications, validity methods, and, of course, the basic ability to submit the form.

Extending an Existing Element

The second way of creating a custom element is to extend an existing element, assuming all of its properties. In this example I’m registering the new‐button element again, but this time including the extends option to say that it should be built on a standard button element, and adding the is attribute to the markup to specify which custom element should be used to extend it:

Using this method, my new‐button element gains all the properties and behaviours of a button without any extra effort on my part. And, if the JS should fail for some reason, the user will still see a regular button, allowing them to submit the form regardless.

You can see a real‐world example of this in action on Github, where they extend the time element:

<time is="relative-time" datetime="…">Jan 27, 2015</time>

When JS is working and the browser supports custom elements, the custom relative‐time element is used and a relative timestamp is shown (‘2 hours ago’); otherwise it falls back to a standard time element, with an absolute datetime (‘Jan 27, 2015’).

The Best Way to Create a Custom Element

To me it’s clear that the second method is superior to the first; in fact, I can’t think of a single drawback to using custom elements in this way.

I’ve put together a quick demo of the custom element extension method, showing an extended button element with a custom prototype. As of this writing, you’ll need Chrome or Opera, or Firefox with the dom.webcomponents.enabled preference on, to see it working.

It’s my opinion that, for as long as there is a dependence on JS for custom elements, we should extend existing elements when writing custom elements. It makes sense for developers, because new elements have access to properties and methods that have been defined and tested for many years; and it makes sense for users, as they have fallback in case of JS failure, and baked‐in accessibility fundamentals.

The only difficulty I can see is in choosing the right element type for a new custom element; for example, if I use the google‐maps element, should I extend a blank div, or perhaps an img with a static map source? This is an important decision, and the result of your choice should always allow the user to complete the task they set out to achieve, albeit in a more limited manner. However, I don’t feel that this is an impediment to adopting the practice.

Conclusion

To be honest, this is not new advice; you may recognise it as good old‐fashioned progressive enhancement. Fallbacks, behaviour and accessibility should be considered every time you enhance or create a new UI element using JavaScript. As Roger Johansson put it:

If you really must make a completely custom select element, make sure to replicate all functionality of a native select. Per platform.

Just because custom elements are a new technology, doesn’t mean we should forget the lessons of the past.

The Future of Custom Elements

As I was preparing this post I was made aware that discussions are ongoing to remove the is/extends method from the custom elements spec, and instead replace them with a purely script‐based upgrading system. There are many reasons for this, you can follow the discussion in the Web Apps mailing list if you’re interested (and have an hour to spare). This is only in talks for now, but seems likely to occur. I’m publishing this post because the information in it is correct as of the publishing date (and, frankly, because it took me a while to write!), but do be aware that this is unlikely to be the method we use in the future. It’s important to note, however, that the broader point about progressive enhancement still stands.

† Note that I’m keeping this simple for the sake of illustration; in a real‐world case I would want to add some behaviour or properties to my button beyond its default, to justify it being a custom element. This is shown in the later example.

Related

7 comments on “Best Practice for Creating Custom Elements”

It looks like the “attachedCallback” function is invoked with the custom element as its context (this value). Therefore, in your demo, you don’t have to query the element, just this.newProperty works.

To me it’s clear that the second method is superior to the first; in fact, I can’t think of a single drawback to using custom elements in this way.

I was very briefly involved in a similar conversation, and while I do agree there are no drawbacks per se, there are limitations.

For example, if I want to create a button that encapsulates the use of flexbox layout to vertically align an icon next to the text label of the button, how do I do that with extends, instead of a fully custom element that contains a button?

If I want to make a custom input element that adds to the shadow DOM of the input, how can I do that?

The backwards compatibility really trumps the limitations, in my mind, but I’m not totally satisfied with extends / @is.

I want to build DOMs for native elements myself while maintaining native functionality.