Best Practice for Creating Custom Elements

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

It looks like cus­tom ele­ments, and web com­po­nents in gen­er­al, are begin­ning to break through into gen­er­al devel­op­er con­scious­ness, as I see more and more arti­cles and talks dis­cussing what they are, what they are good for, and how to make them.

As they’re not yet being used heav­i­ly in devel­op­ment, how­ev­er, I think there’s a good oppor­tu­ni­ty to define best prac­tices in the way we use them. In this post I want to pro­pose a best prac­tice method for writ­ing cus­tom ele­ments: I’ll do that by com­par­ing two meth­ods for cre­at­ing cus­tom ele­ments, along with the advan­tages and draw­backs of each.

Aside: it strikes me that I haven’t writ­ten about cus­tom ele­ments here on my own blog, despite hav­ing giv­en a few talks and writ­ten a few pub­lished arti­cles on the sub­ject. In case you’re not sure what they are, I rec­om­mend you read my Detailed Intro­duc­tion To Cus­tom Ele­ments first.

Impor­tant Note: Please be sure to read the Future of Cus­tom Ele­ments sec­tion at the end of this post for some crit­i­cal news about the sta­tus of cus­tom elements.

A New Custom Element

The first way to cre­ate a cus­tom ele­ment is from scratch, reg­is­ter­ing a com­plete­ly new ele­ment with the DOM. In this exam­ple I’m reg­is­ter­ing, then imple­ment­ing, a new ele­ment which I will use as a but­ton†. I’m call­ing my ele­ment new-button, as I am a man of lim­it­ed imagination:

//JS file
document.registerElement('new-button');
<!-- HTML file -->
<new-button>Send</new-button>

I think this has two major draw­backs. First, should JS not be work­ing in the user’s brows­er, this ‘but­ton’ will do noth­ing; all that the user will see is the text inside the ele­ment. This will include not just cas­es where JS has failed or been dis­abled, but also where it hasn’t yet loaded (big JS file, slow net­work connection).

Sec­ond, and more oner­ous­ly for me, this new ele­ment would use the stan­dard HTM­LEle­ment API inter­face, so to make it act as a but­ton I would have to man­u­al­ly add all the unique prop­er­ties and meth­ods of a but­ton: all of the form prop­er­ties, acces­si­bil­i­ty fea­tures, behav­iour indi­ca­tions, valid­i­ty meth­ods, and, of course, the basic abil­i­ty to sub­mit the form.

Extending an Existing Element

The sec­ond way of cre­at­ing a cus­tom ele­ment is to extend an exist­ing ele­ment, assum­ing all of its prop­er­ties. In this exam­ple I’m reg­is­ter­ing the new-button ele­ment again, but this time includ­ing the extends option to say that it should be built on a stan­dard button ele­ment, and adding the is attribute to the markup to spec­i­fy which cus­tom ele­ment should be used to extend it:

//JS file
document.registerElement('new-button', {
  extends: 'button'
});
<!-- HTML file -->
<button is="new-button">Send</button>

Using this method, my new-button ele­ment gains all the prop­er­ties and behav­iours of a but­ton with­out any extra effort on my part. And, if the JS should fail for some rea­son, the user will still see a reg­u­lar but­ton, allow­ing them to sub­mit the form regardless.

You can see a real-world exam­ple of this in action on Github, where they extend the time element:

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

When JS is work­ing and the brows­er sup­ports cus­tom ele­ments, the cus­tom relative-time ele­ment is used and a rel­a­tive time­stamp is shown (‘2 hours ago’); oth­er­wise it falls back to a stan­dard time ele­ment, with an absolute date­time (‘Jan 27, 2015’).

The Best Way to Create a Custom Element

To me it’s clear that the sec­ond method is supe­ri­or to the first; in fact, I can’t think of a sin­gle draw­back to using cus­tom ele­ments in this way.

I’ve put togeth­er a quick demo of the cus­tom ele­ment exten­sion method, show­ing an extend­ed but­ton ele­ment with a cus­tom pro­to­type. As of this writ­ing, you’ll need Chrome or Opera, or Fire­fox with the dom.webcomponents.enabled pref­er­ence on, to see it working.

It’s my opin­ion that, for as long as there is a depen­dence on JS for cus­tom ele­ments, we should extend exist­ing ele­ments when writ­ing cus­tom ele­ments. It makes sense for devel­op­ers, because new ele­ments have access to prop­er­ties and meth­ods that have been defined and test­ed for many years; and it makes sense for users, as they have fall­back in case of JS fail­ure, and baked-in acces­si­bil­i­ty fundamentals.

The only dif­fi­cul­ty I can see is in choos­ing the right ele­ment type for a new cus­tom ele­ment; for exam­ple, if I use the google-maps ele­ment, should I extend a blank div, or per­haps an img with a sta­t­ic map source? This is an impor­tant deci­sion, and the result of your choice should always allow the user to com­plete the task they set out to achieve, albeit in a more lim­it­ed man­ner. How­ev­er, I don’t feel that this is an imped­i­ment to adopt­ing the practice.

Conclusion

To be hon­est, this is not new advice; you may recog­nise it as good old-fash­ioned pro­gres­sive enhance­ment. Fall­backs, behav­iour and acces­si­bil­i­ty should be con­sid­ered every time you enhance or cre­ate a new UI ele­ment using JavaScript. As Roger Johans­son put it:

If you real­ly must make a com­plete­ly cus­tom select ele­ment, make sure to repli­cate all func­tion­al­i­ty of a native select. Per platform.

Just because cus­tom ele­ments are a new tech­nol­o­gy, doesn’t mean we should for­get the lessons of the past.

The Future of Custom Elements

As I was prepar­ing this post I was made aware that dis­cus­sions are ongo­ing to remove the is/extends method from the cus­tom ele­ments spec, and instead replace them with a pure­ly script-based upgrad­ing sys­tem. There are many rea­sons for this, you can fol­low the dis­cus­sion in the Web Apps mail­ing list if you’re inter­est­ed (and have an hour to spare). This is only in talks for now, but seems like­ly to occur. I’m pub­lish­ing this post because the infor­ma­tion in it is cor­rect as of the pub­lish­ing date (and, frankly, because it took me a while to write!), but do be aware that this is unlike­ly to be the method we use in the future. It’s impor­tant to note, how­ev­er, that the broad­er point about pro­gres­sive enhance­ment still stands.

Many thanks to Stu­art Lan­gridge, Bruce Law­son and Patrick Lauke for san­i­ty-check­ing this arti­cle, which was inspired by a short Twit­ter con­ver­sa­tion with Stu­art and the blog post On the acces­si­bil­i­ty of web com­po­nents. Again. by Bruce.

† Note that I’m keep­ing this sim­ple for the sake of illus­tra­tion; in a real-world case I would want to add some behav­iour or prop­er­ties to my but­ton beyond its default, to jus­ti­fy it being a cus­tom ele­ment. This is shown in the lat­er example.

7 comments on
“Best Practice for Creating Custom Elements”

  1. It looks like the “attached­Call­back” func­tion is invoked with the cus­tom ele­ment as its con­text (this val­ue). There­fore, in your demo, you don’t have to query the ele­ment, just this.newProperty works.

  2. Good point. I’ve sim­pli­fied the demo, thanks.

  3. […] Best Prac­tice for Cre­at­ing Cus­tom Ele­ments by Peter Gasston, “With a very impor­tant note about the future of the method”. […]

  4. […] Best Prac­tice for Cre­at­ing Cus­tom Elements […]

  5. To me it’s clear that the sec­ond method is supe­ri­or to the first; in fact, I can’t think of a sin­gle draw­back to using cus­tom ele­ments in this way.

    I was very briefly involved in a sim­i­lar con­ver­sa­tion, and while I do agree there are no draw­backs per se, there are limitations.

    For exam­ple, if I want to cre­ate a button that encap­su­lates the use of flexbox lay­out to ver­ti­cal­ly align an icon next to the text label of the but­ton, how do I do that with extends, instead of a ful­ly cus­tom ele­ment that con­tains a button?

    If I want to make a cus­tom input ele­ment that adds to the shad­ow DOM of the input, how can I do that?

    The back­wards com­pat­i­bil­i­ty real­ly trumps the lim­i­ta­tions, in my mind, but I’m not total­ly sat­is­fied with extends / @is.

    I want to build DOMs for native ele­ments myself while main­tain­ing native functionality.

  6. […] Лучший способ создания новых элементов […]

  7. Great post thank you