Saturday, April 26, 2014

How to Create Custom HTML Elements

Custom HTML Elements
Photo by Kenny Louie / Flickr

An exciting feature of the HTML specification that's been getting a bit of hype recently is custom HTML elements. These allow you to create your own HTML elements along with their own JavaScript API. This can be useful when building interfaces with components that are reused throughout an application.

In this blog post you're going to learn how to create your own HTML elements and define a JavaScript API for them.

Let's get started.

Creating Custom HTML Elements

The document.registerElement() method is used to create a custom HTML element. This should be passed as the name of your custom element along with an (optional) object that defines the API.

In the following example we simply create a new HTML element called <x-treehouse> and then add it to the page.

var XTreehouseElement = document.registerElement('x-treehouse');
document.body.appendChild(new XTreehouseElement());

This would add the following HTML to the end of the <body> element:


The name of your custom element must contain a dash (-) so that the browsers parser can determine between standard and custom HTML elements. This also means that you won't encounter problems if a new HTML element is introduced that uses the same name as your custom element.

Creating a JavaScript API For Your Element

You can define a JavaScript API for your custom element that consists of a number of methods and properties. To do this, start by creating a new JavaScript object. This can be done using the Object.create() method. Passing HTMLElement.prototype to this method will create an object with the standard set of methods and properties available to HTML elements.

var XTreehouseProto = Object.create(HTMLElement.prototype);

You can then define your custom methods on this new object, as shown below.

XTreehouseProto.hello = function() {

To define a property for your custom element you can use the Object.defineProperty() method. The first parameter should be your prototype object; the second is the name of the property; and the third should be an object describing the behaviour of that property. This is where you can set a default value as well as specify whether the property is writable or read-only.

Object.defineProperty(XTreehouseProto, 'badges', { 
    value: 20,
    writable : true

Once you've defined the API for your custom element, you need to call document.registerElement(). Use the name of your custom element as the first parameter and then pass in an object with a property named prototype. The value of this property should be set to the prototype object you created earlier.

var XTreehouseElement = document.registerElement('x-treehouse',  { 
    prototype: XTreehouseProto

Once you've registered your custom element, you can create a new instance of the element and add it to the page.

var xtreehouse = new XTreehouseElement();

The methods and properties you defined earlier can be accessed just as you would on any other HTML element.

var badges = xtreehouse.badges;

Extending Existing Elements

As well as creating your own custom elements, you can also use the registerElement() method to extend the functionality of existing HTML elements. Let's extend the <img> element to create a variation for displaying thumbnail images.

You start by creating a prototype object as we did before. This time, however, you want to copy the prototype object of the element you are extending. In this case, that will be the HTMLImageElement.

var ThumbImageProto = Object.create(HTMLImageElement.prototype);

Next you define a function for the createdCallback, which is fired when the element is created. Here we can set the width and height of the image.

ThumbImageProto.createdCallback = function() {
    this.width = '100';
    this.height = '100';

You can also define custom methods and properties as before.

ThumbImageProto.changeImage = function() {
    this.src = 'new-image.jpg';

When extending an existing element, you need to add the extends property to your options object in the call to document.registerElement(). This property should be set to the name of the element you are extending.

var ThumbImage = document.registerElement('thumb-img', {
    prototype: ThumbImageProto,
    extends: 'img'

To use your custom element, you can now specify an is attribute on the element that you have extended. Setting the value of this attribute to the name of your custom element will tell the browser that this <img> element should use the API defined for thumb-img.

<img is="thumb-img">

Custom Element Callback Methods

There are a number of callbacks that you can listen for when creating and managing your custom elements.

  • createdCallback – Called when a custom element is created.
  • attachedCallback – Called when a custom element is inserted into the DOM.
  • detachedCallback – Called when a custom element is removed from the DOM.
  • attributeChangedCallback(attrName, oldValue, newValue) – Called when an attribute on a custom element changes.

You specify functions for these callbacks on the prototype object that's passed to document.registerElement().

var XTreehouseProto = Object.create(HTMLElement.prototype);

XTreehouseProto.createdCallback = function() {}
XTreehouseProto.attachedCallback = function() {}
XTreehouseProto.detachedCallback = function() {}
XTreehouseProto.attributeChangedCallback = function(attrName, oldValue, newValue) {}

var XTreehouse = document.registerElement('x-treehouse',  { prototype: XTreehouseProto });

Custom Elements with Shadow DOM

The true power of custom elements becomes clear when you think about how they can be used alongside Shadow DOM. This makes it really easy to create reusable interface components.

Custom HTML Elements
Using custom elements and shadow DOM to create reusable product cards.

In this section we're going to look at an example of how you can use custom elements and Shadow DOM to create an interface component for displaying products in a web store. The idea here is that a web developer can easily create new products by adding a single line of HTML to their markup. All of the information needed to display the product is contained within data- attributes on the custom element.

<x-product data-name="Product Name" data-img="image.png" data-url=""></x-product>

We'll start by create a new prototype object based off of HTMLElement.prototype.

// Create a new object based of the HTMLElement prototype
var XProductProto = Object.create(HTMLElement.prototype);

Next we need to set up a function for createdCallback. This is where we will create the <img> and <a> elements that are responsible for displaying the product. I'll show you the code first and then walk you through it.

// Set up the element.
XProductProto.createdCallback = function() {
    // Create a Shadow Root
    var shadow = this.createShadowRoot();

    // Create an img element and set it's attributes.
    var img = document.createElement('img');
    img.alt = this.getAttribute('data-name');
    img.src = this.getAttribute('data-img');
    img.width = '150';
    img.height = '150';
    img.className = 'product-img';

    // Add the image to the Shadow Root.

    // Add an event listener to the image.
    img.addEventListener('click', function(e) {
        window.location = this.getAttribute('data-url');

    // Create a link to the product.
    var link = document.createElement('a');
    link.innerText = this.getAttribute('data-name');
    link.href = this.getAttribute('data-url');
    link.className = 'product-name';

    // Add the link to the Shadow Root.

Here we start by creating a new Shadow Root. We then create an <img> element and set its alt, src, height and width attributes using the information specified on the x-product element.

Note: Inside the callback function, this refers to the custom element in your markup.

Next we add the <img> element to the shadow root and create a new <a> element. Again we set the attributes on the element using information from the data- attributes on the custom element. To finish up we add the <a> element we just created to the shadow root.

Now we need to register the custom element. Call document.registerElement() passing in x-product as the element name and specifying the XProductProto object as the prototype.

// Register the new element.
var XProduct = document.registerElement('x-product', {
    prototype: XProductProto

That concludes all the JavaScript code that's needed to get this demo working. Let's add a bit of CSS to style the product items.

x-product {
    display: inline-block;
    float: left;
    margin: 0.5em;
    border-radius: 3px;
    box-shadow: 0 1px 3px rgba(0,0,0,0.3);
    font-family: Helvetica, arial, sans-serif;
    -webkit-font-smoothing: antialiased;

x-product::shadow .product-img {
    cursor: pointer;
    background: #FFF;
    margin: 0.5em;

x-product::shadow .product-name {
    display: block;
    text-align: center;
    text-decoration: none;
    color: #08C;
    border-top: 1px solid #EEE;
    font-weight: bold;
    padding: 0.75em 0;

To display a product, we just need to add an <x-product> element to the HTML markup. The product data is set using the data-name, data-img and data-url attributes. When the page loads, the browser will recognise these as custom elements and fire the createdCallback event for each of them.

<x-product data-name="Ruby" data-img="" data-url=""></x-product>
<x-product data-name="JavaScript" data-img="" data-url=""></x-product>
<x-product data-name="Python" data-img="" data-url=""></x-product>

That's it! You've now created a demo application that uses custom elements and shadow DOM to display a series of products.

Check out the demo to see this in action.

View Demo

Note: This demo requires Shadow DOM, which is only supported in Chrome Canary.

Browser Support for Custom HTML Elements

Google Chrome (version 33+) and Opera are the only browsers with support for custom elements at the moment. However, there is a great polyfill available from the Polymer project that will add support for custom elements to other browsers. The x-tags polyfill maintained by Mozilla is also very useful.

Download the Polymer customelements Polyfill

You can check to see if the user's browser supports custom elements by looking for the registerElement() method on the document object.

if ('registerElement' in document) {
    // Supported.
} else {
    // Not supported.

Posted at Treehouse by