Extending HTML by Creating Custom Tags
In this tutorial, I will show you how easy it is to extend the HTML language with custom tags. The custom tags can be used to implement various behaviors, so they are a very convenient way to write less code and keep your HTML documents simpler.
To go further with custom tags, check out my post on creating encapsulated custom tags with the Shadow DOM.
What Is a Custom HTML Tag?
With HTML, you use the <b>
tag, for example, to show bold text. If you need a list, then you use the <ul>
tag with its child tag <li>
for each list item. Tags are interpreted by browsers and, together with CSS, determine how the content of a webpage is displayed and also how parts of the content behave.
Sometimes, just using one HTML tag is not enough for the functionality needed in web applications. Usually, this is solved by using multiple HTML tags together with JavaScript and CSS, but this solution is not so elegant.
A more elegant solution would be to use a custom tag—an identifier enclosed in <>
which is interpreted by the browser to render our intended functionality. As with regular HTML tags, we should be able to use a custom tag multiple times on a page, and we should also be able to have tag attributes and sub-tags to aid the functionality of the custom tag.
In simpler terms, custom elements can be used to unlock the following features:
- define and build new HTML elements
- build elements that extend other elements
- create elements that can put together custom functionalities in your application
- use existing DOM elements and extend their API
In this post, we are going to learn about custom elements by building a custom tag called <codingdude-gravatar>
. This custom tag will display the Gravatar picture for a certain email address.
The customElements
API in modern browsers makes this easy!
1. Creating the Project
To implement and test our tag, we will need to create a few things:
- an index.html file to use the custom tag
- a codingdude-gravatar.js file to implement the custom tag
2. Modifying index.html
Let's edit the index.html file and make its content look like this:
1 |
<!DOCTYPE html>
|
2 |
<html>
|
3 |
<head>
|
4 |
<script type="text/javascript" src="codingdude-gravatar.js"></script> |
5 |
</head>
|
6 |
<body>
|
7 |
This is my Gravatar picture: |
8 |
<codingdude-gravatar email="admin@coding-dude.com"></codingdude-gravatar> |
9 |
</body>
|
10 |
</html>
|
If we load index.html in the browser now, the result is not that impressive because we have yet to implement the code for our custom tag.
One thing to notice is that the browser is very forgiving, so you can have unknown tags in a document, and the browser will just ignore them. To have our custom tag actually display the Gravatar picture for my email, we have to first understand how Gravatar works.
3. Registering the New Tag
Step 1: Choose the Right Name
In order to create a custom tag, you need to make use of window.customElements.define()
.
The very first argument in customElements.define()
is the name of the tag. This is what we have mentioned in the HTML file. The name should always have a dash (-). Tags with names like <codingdudeGravatar>
are invalid. This allows the parser should be able to differentiate a regular element from a custom tag.
Step 2: Choose the Right Prototype
customElements.define()
has a second optional object. This object is used to mention the prototype of the new element. By default, the custom elements inherit from HTMLElement
. The above piece of code is equivalent to the following:
1 |
customElements.define("codingdude-gravatar", CodingDudeGravatar); |
If you wish to create a custom element, that extends another HTML element, the native element has to be extended in customElements.define()
. Custom elements that inherit native elements are also known as "type extension custom elements".
1 |
customElements.define("codingdude-gravatar", class extends HTMLButtonElement {...}); |
2 |
A couple more things to watch out for:
- You cannot register the tag multiple times. If you try this, the browser will throw a
DOMException
. - Custom element tags cannot be self-closing.
4. Instantiating the Custom Element
All standard rules of HTML elements apply to custom elements. Just like standard elements, you can create a custom element in the DOM using JavaScript, or declare it in HTML.
Declaring custom elements in HTML:
1 |
<codingdude-gravatar></codingdude-gravatar>
|
Creating a custom element in the DOM using JavaScript:
1 |
var codingdudeGravatar = document.createElement('codingdude-gravatar'); |
2 |
codingdudeGravatar.addEventListener('click',function(e){}); |
Instantiating type extension elements in HTML:
1 |
<button is="codingdude-gravatar"> |
Creating a type extension element in DOM using JavaScript:
1 |
var codingdudeGravatar = document.createElement('button', 'codingdude-gravatar'); |
5. Adding Markup to the Custom Tag
Adding markup is rather simple in a custom tag. To begin with, you need to create a class that extends a parent HTML element. In our case, we are going to extend HTMLElement
. Inside the HTMLElement
, we'll make use of the constructor
to add an event listener and adjust the innerText
of the custom tag. Remember to call super()
because that will help inherit the methods and properties of the parent class. Always remember, this
inside the constructor points to the custom element that gets created.
Here is a simple overview of how our component would look.
1 |
class CodingDudeGravatar extends HTMLElement { |
2 |
constructor() { |
3 |
super() |
4 |
this.addEventListener('click', e => { |
5 |
alert('You Clicked Me!') |
6 |
});
|
7 |
this.innerText="Hello There!" |
8 |
}
|
9 |
}
|
10 |
window.customElements.define('codingdude-gravatar', CodingDudeGravatar); |
6. LifeCycle Methods
Before you start adding markup to the custom tag, you need to be aware of the lifecycle methods associated with custom tags. There are four lifecycle callbacks.
Callback | Purpose |
---|---|
constructor |
an instance of the custom tag element gets created |
connectedCallback |
an instance of the custom tag element gets inserted into the document |
disconnectedCallback |
an instance of the custom tag element gets deleted from the document |
attributeChangedCallback(attributeName, oldValue, newValue) |
an attribute in the custom tag element was added, removed, or updated |
The skeleton of our custom tag with these callbacks will look as follows:
1 |
class CodingDudeGravatar extends HTMLElement { |
2 |
constructor() { |
3 |
super(); // always call super() first in the constructor. |
4 |
...
|
5 |
}
|
6 |
connectedCallback() { |
7 |
...
|
8 |
}
|
9 |
disconnectedCallback() { |
10 |
...
|
11 |
}
|
12 |
attributeChangedCallback(attrName, oldVal, newVal) { |
13 |
...
|
14 |
}
|
15 |
}
|
We have already seen how to use the constructor
in a custom element. Now, let's use the other callback methods in our code! Let's begin with connectedCallback
.
connectedCallback
The connectedCallback
can be used to check for the email attribute in our custom tag. As soon as the element gets added to the document, this check will take place. We would make use of a getter
function to check if the custom tag
has the email
attribute or not.
1 |
class CodingDudeGravatar extends HTMLElement { |
2 |
get email() { |
3 |
return this.hasAttribute('email'); |
4 |
}
|
5 |
constructor() { |
6 |
super() |
7 |
this.addEventListener('click', e => { |
8 |
alert('You Clicked Me!') |
9 |
});
|
10 |
this.innerText="Hello There!" |
11 |
}
|
12 |
connectedCallback(){ |
13 |
if(this.email){ |
14 |
var email = this.attributes.email.value; |
15 |
var gravatar = "https://www.gravatar.com/avatar" |
16 |
this.innerHTML = "<img src='"+gravatar+"'></br>"+email |
17 |
}
|
18 |
else
|
19 |
{
|
20 |
this.innerHTML = "No Email"; |
21 |
}
|
22 |
}
|
23 |
}
|
When the custom tag is inserted in the HTML, you'll see something like this:
1 |
<codingdude-gravatar></codingdude-gravatar>
|
However, when the email
attribute is set in the custom tag, the screen would be as follows:
1 |
<codingdude-gravatar email="admin@coding-dude.com"></codingdude-gravatar> |



attributeChangedCallback
The browser would call the lifecycle method attributeChangedCallback
for all attributes listed in the observedAttributes
array. The goal behind this is to improve the performance of the custom tag. For example, if the user decides to change a style, you would not want the attributeChangedCallback
to be fired. In our case, we would want the attributeChangedCallback
to be called only when the email changes. To achieve this, the code would appear as follows:
1 |
class CodingDudeGravatar extends HTMLElement { |
2 |
get email() { |
3 |
return this.hasAttribute('email'); |
4 |
}
|
5 |
constructor() { |
6 |
super() |
7 |
this.addEventListener('click', e => { |
8 |
alert('You Clicked Me!') |
9 |
});
|
10 |
this.innerText="Hello There!" |
11 |
}
|
12 |
connectedCallback(){ |
13 |
if(this.email){ |
14 |
var email = this.attributes.email.value; |
15 |
var gravatar = "http://www.gravatar.com/avatar" |
16 |
this.innerHTML = "<img src='"+gravatar+"'></br>"+email |
17 |
}
|
18 |
else
|
19 |
{
|
20 |
this.innerHTML = "No Email"; |
21 |
}
|
22 |
}
|
23 |
static get observedAttributes() { |
24 |
return ['email']; |
25 |
}
|
26 |
attributeChangedCallback(name, oldValue, newValue) { |
27 |
if(name == 'email'){ |
28 |
alert(`The ${name} changed to ${newValue}`) |
29 |
}
|
30 |
}
|
31 |
}
|
Based on the above piece of code, an alert will be seen every time we change the email.



The callbacks in custom elements are synchronous. If you call el.setAttribute('email','newemail')
on the custom element, the browser will trigger attributeChangedCallback()
immediately.
disconnectedCallback
Last in our list of lifecycle methods would be the disconnectedCallback
. The moment you remove the element from the document, this method will be called. The method is extremely useful when you want to clean up the effects caused by a custom tag. You can remove custom tags easily, using the el.remove()
method.
However, you must be very careful about how disconnectedCallback()
is used. This callback will never be triggered if the user chooses to close the tab or browser.
7. Properties to Attributes
In any HTML-based app, it is quite common for developers to use properties that reflect back on the DOM. For example, hidden
is a property that can be used to hide an element, e.g. <div hidden></div>
would just hide the element from the DOM. It is natural for developers to want these properties to work on custom tags as well.
Since we extend from an HTMLElement
, these properties will be present in the custom element by default. However, the behaviour of these properties can be modified. Most of the time, the getters and setters in JavaScript classes are used to control the properties.
1 |
codingdude-gravatar[hidden] { |
2 |
opacity: 0.5; |
3 |
pointer-events: none; |
4 |
} |
5 |
|
6 |
...inside the class |
7 |
{ |
8 |
get hidden() { |
9 |
return this.hasAttribute('hidden'); |
10 |
} |
11 |
|
12 |
set hidden(val) { |
13 |
// Reflect the value of `hidden` as an attribute. |
14 |
if (val) { |
15 |
this.setAttribute('hidden', ''); |
16 |
} else { |
17 |
this.removeAttribute('hidden'); |
18 |
} |
19 |
} |
20 |
} |
Demo
Everything discussed in this post can be experimented with in the following demo.
Conclusion
Hooray! We have come to the end of our post on how to create custom elements. Now, you should be able to create a custom element and use it in your HTML markup. Do give it a try! You will be astonished to see how much can be accomplished.
This is just the beginning of how custom elements work. There is so much more to explore and learn in this area. Keep watching this space for more posts on custom elements and their advanced concepts.