Custom Elements, a beginner’s guide

Sam Thorogood

Let’s talk about Web Components: specifically, Custom Elements.

Web development in 2016 is still largely the process of composing regular old elements — like div, span, or semantic elements like section and header — to generate your desired outcome.

There’s a number of wrappers around this flow: either template engines, “embedded complex languages” like PHP, or libraries to manage your HTML.

Custom Elements allow you to encapsulate functionality and behavior in a custom HTML element.

These won’t replace the traditional approaches completely. But they’re a tool for 2016-era web development — just like any other you have at your disposal. And best of all, when done well, they compose well with any other technology stack — they’ll just be like any native tag.


A Quick Example

On nearly every GitHub page, you’ll see text like “2 days ago”, “3 minutes ago”, or “4 years ago”. However, this isn’t how the server renders the page: it sends down HTML like-

<time-ago datetime="2014-08-05T04:31:10Z">Aug 4, 2014</time-ago>

What’s this time-ago element? Turns out, it’s defined by GitHub’s time element extensions. It simply converts absolute times to relative ones by replacing the element’s text content.

For GitHub, this solves a specific problem — if the server generated relative dates in HTML, long-lived caches would rapidly become invalid.


So let’s say you want to display a random kitten image on every load. You insert a new element with some suitable attributes-

<kitten-image width="400" height="200" cute="lots"></kitten-image>

This element shows up, and you can style it with CSS. But what now?

I guess you could add a DOMContentReady listener, and use querySelectorAll to add a native image to every element. It’s a bit ugly — not to mention if you want to dial back the cuteness at a later moment. 😾

Using Custom Elements, you’ll call document.registerElement (and in practical terms, you can use a polyfill and be all set for most older browsers) with a prototype object describing your behavior. First, an aww-dorable example —

demo of kitten-image, thanks to placekitten.com

Here’s an abridged version of the JS for the kitten element —

var KittenImageElement = Object.create(HTMLElement.prototype);
KittenImageElement.createdCallback = function() {
// set the .innerHTML and do other work
};
document.registerElement('kitten-image', {
prototype: KittenImageElement
});

What does this do? It tells the browser (or polyfill) to call your lifecycle methods (defined on KittenImageElement) when certain things happen — your element was created, added, removed, or its attributes changed. Using these, you can update content, make AJAX requests, etc.


To actually build real elements you’ll want to use, you have to make some choices. One is a bit extreme — you could stop reading this post right now, and head over to Polymer’s website, which is an opinionated framework based entirely on Web Components.

But I’ll assume you’re still here.

The 2016-era Dilemma

Q: What is the best way to work with HTML which lives inside your element? Specifically, what will happen to this HTML?

<kitten-image>
<div>Hello!</div><!-- what happens to this? -->
</kitten-image>

A: Because the kitten example sets innerHTML, our page content is going to be removed in favor of kitten images.

Depending on your use-case, this might be fine. But — as I mentioned in the very first paragraph, we use HTML elements by composing them together. If we eradicate the content of your custom elements on load — you’re not going to be making much music.

What are the approaches to solving this?

1. Moving HTML around

Instead of just removing the inner contents, let’s reparent it. Grab a reference to the children of the custom element, replace the element’s innerHTML, and then reattach the children somewhere in the root. You might end up with something like this —

<kitten-image>
<img src="..." />
<div class="caption">
<div>Hello!</div>
</div>
</kitten-image>

This works well, but c0uld become painful if you want to move your element around or change the caption later. As an aside, this is largely the approach taken by Polymer (at least with its default ‘shady DOM’).

2. Use Shadow DOM

Shadow DOM is a way to give an element custom HTML which isn’t visible in the DOM. It lets you retain content — or just show custom HTML — in a ‘shadow root’. To expand on the above example, you might have —

<kitten-image>
::shadow
<img src="..." />
<div id="caption">
<!-- all 'real' tags, like the div 'Hello!'
below, will appear where this slot tag is -->
<slot></slot>
</div>
<div>Hello!</div>
</kitten-image>

This is the future, but — there’s no active polyfill (the WebComponents.org one is for the old v0 version) and browser support isn’t great.

3. Don’t

Depending on your goal, you may find a custom element has lots of utility even without its own HTML. You can imagine a ‘drag and drop’ element, which allows its contents to be reordered.

For example, this element could just provide JS to manipulate its direct children — children specified at page load time, or at runtime through appendChild.

Alternatively, you can prescribe HTML. It’s less useful than a custom element which creates itself, but you can imagine requiring the kitten image to be written out like this —

<kitten-image>
<img />
<div id="caption">
Hello!
</div>
</kitten>

On being created, your element would look for the image container, caption etc, and not create it on its own.


If you’d like to interop with other libraries or frameworks when building custom elements, it’s actually pretty trivial — just ensure the element is registered, and then create it in a template or other environment. If you’re using Angular 1, just bind with ng-attr-foo

<kitten-image ng-attr-cute="{{cuteness}}"></kitten-image>

If you’re using Polymer, you can bind in a similar way —

<kitten-image cute="{{cuteness}}"></kitten-image>

And if you’d like to trigger two-way binding inside Polymer — perhaps the cute attribute is set by the element, and not the other way around — just be sure to emit a ‘cute-changed’ custom event when the value updates.


I will note: I’m always impressed GitHub’s millions of users are shown custom elements on nearly all their pages without controversy. This alone makes me confident they will succeed in some form.

That’s it

If you’re looking for more Web Components and Custom Elements resources — Google is a good source, but note —

  • Try to avoid Shadow DOM from 2015 or earlier — this is because the v0 spec, only supported by Chrome, is still well-documented. If you see createShadowRoot, it’s deprecated — attachShadow from v1 is not.
  • I’ve not covered styling in any big way, but even without Custom Elements, you can write style rules targeting any element name, even ones you’ve made up.
  • Web Components and Custom Elements ≠ Polymer.

The last point is important. Remember, Web Components are the standard — the code you see above works anywhere, with any framework or library —so just build what you like, and use it anywhere you want.

Sam Thorogood

Written by

🎅🎄 Santaware Engineer at Google in sunny Sydney, Australia 🇦🇺 —evangelizes Chrome and the mobile web!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade