Tutorial: How to create a Web Component?

Welcome back to the Web Components 101 Series! We’re going to discuss the state of Web Components, provide expert advice, give tips and tricks and reveal the inner workings of Web Components.

In today’s tutorial, we’re going to teach you the fundamentals of Web Components by building a <name-tag> component step by step!

First, we have to learn the rules. Then, we’re going to set up our development environment.

Next, we’ll define a new HTML element, going to learn how to pass attributes, create and use the Shadow DOM, and use HTML templates.

About the author

☞ If you like this article, please support me by buying me a coffee ❤️.

Other posts in the Web Components 101 series

What are we going to build today?

The basic rules

These are the rules:

  1. You can’t register a Custom Element more than once.
  2. Custom Elements cannot be self-closing.
  3. To prevent name clashing with existing HTML elements, valid names should:
  • Always include a hyphen (-) in its name.
  • Always be lower case.
  • Not contain any uppercase characters.

Setting up our development environment

Steps to set up our dev env

  1. Click the Fork button in the top right of the screen to create your copy.
  2. Profit! Your environment is set up successfully.

Defining a new HTML Element

<!DOCTYPE html>
<meta charset="UTF-8" />
<script src="./dist/name-tag.js" type="module"></script> </head>
<h3>Hello World</h3>
<name-tag></name-tag> </body>

We include our component with a <script>. This allows us to use our component, just like any other HTML elements in the <body> of our page.

But we don’t see anything yet, our page is empty. This is because our name tag isn’t a proper HTML Tag (yet). We have to define a new HTML Element and this is done with JavaScript.

  1. Open name-tag.js and create a class that extends the base HTMLElement class.
  2. Call super() in the class constructor. Super sets and returns the component's this scope and ensures that the right property chain is inherited.
  3. Register our element to the Custom Elements Registry to teach the browser about our new component.

This is how our class should look like:

class UserCard extends HTMLElement {
constructor() {
customElements.define('name-tag', UserCard);

Congrats! You’ve successfully created and registered a new HTML Tag!

Passing values to the component with HTML attributes

Attributes provide extra information about HTML tags, always come in name/value pairs, and could only contain strings.

First, we have to add an name attribute to the <name-tag> in index.html. This enables us to pass and read the value from our component

<name-tag name="John Doe"></name-tag>

Now that we’ve passed the attribute, it’s time to retrieve it! We do this with the Element.getAttribute() method that we add to the components constructor().

Finally, we’re able to push the attribute’s value to the component's inner HTML. Let’s wrap it between a <h3>.

This is how our components class should look like:

class UserCard extends HTMLElement {
constructor() {
this.innerHTML = `<h3>${this.getAttribute('name')}</h3>`;

Our component now outputs “John Doe”.

Add global styling

Add the following CSS to the <head> in index.html and see that the component's heading color changes to Rebecca purple:

h3 {
color: rebeccapurple;

Create and use the Shadow DOM

  1. Add this.attachShadow({mode: 'open'}); to the component's constructor (read more about Shadow DOM modes here).
  2. We also have to attach our innerHTML to the shadow root. Replace this.innerHTML with this.shadowRoot.innerHTML.

Here is the diff of our constructor:

constructor() {
this.attachShadow({mode: 'open'});
- this.innerHTML = `<h3>${this.getAttribute('name')}</h3>`;
+ this.shadowRoot.innerHTML = `<h3>${this.getAttribute('name')}</h3>`;

Notice that the global styling isn’t affecting our component anymore. The Shadow DOM is successfully attached and our component is successfully encapsulated.

Create and use HTML Templates

First, we have to create a const template outside our components class in name-tag.js, create a new template element with the Document.createElement() method and assign it to our const.

const template = document.createElement('template');
template.innerHTML = `
h3 {
color: darkolivegreen; //because I LOVE olives
<div class="name-tag">

With the template in place, we’re able to clone it to the components Shadow Root. We have to replace our previous “HTML Template” solution.

...class UserCard extends HTMLElement {
this.attachShadow({mode: 'open'});
- this.shadowRoot.innerHTML = `<h3>${this.getAttribute('name')}</h3>`;
+ this.shadowRoot.appendChild(template.content.cloneNode(true));

What about passing attributes?!

We have to get out attribute’s value to the template somehow. We don’t have direct access to the scope of the component in the template, so we have to do it differently.

<div class="name-tag">

This won’t work since we don’t have access to the component’s scope in the template.

We have to query the Shadow DOM for the desired HTML Element (i.e. <h3>) and push the value of the attribute to its inner HTML.

constructior() {
this.shadowRoot.querySelector('h3').innerText = this.getAttribute('name');

The result is that we see “John Doe” again on our page and this time, it’s colored differently and the heading on the main page stays Rebecca purple! The styling we’ve applied works like a charm and is contained to the Shadow DOM. Just like we wanted to: No leaking of styles thanks to our component’s encapsulating properties.

Bonus: Update styles

.name-tag {
padding: 2em;
border-radius: 25px; background: #f90304; font-family: arial;
color: white;
text-align: center;
box-shadow: 2px 2px 5px 0px rgba(0, 0, 0, 0.75);
h3 {
padding: 2em 0;
background: white;
color: black;
p {
font-size: 24px;
font-weight: bold;
text-transform: uppercase;

Closing thoughts about how to create a Web Component from scratch

I hope this tutorial was useful and I hope to see you next time!

Stefan Nieuwenhuis is software engineer, loves to play sports, read books and occasionally jump out of planes (with a parachute that is).

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store