[tutorial] How to create a Web Component?
Tags: javascript, programming, webcomponents, webdev
Published: 2021-05-25 16:32:54

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](https://nhswd.com/blog/web-components-101-what-are-web-components/#3-shadow-dom), and use [HTML templates](https://nhswd.com/blog/web-components-101-what-are-web-components/#2-html-templates).

## About the author
Stefan is a JavaScript Web Developer with more than 10 years of experience. He loves to play sports, read books and occasionally jump out of planes (with a parachute that is).
☞ If you like this article, please support me by [buying me a coffee](https://www.buymeacoffee.com/stefannhs) ❤️.

## Other posts in the Web Components 101 series

- [What are Web Components?](https://nhswd.com/blog/web-components-101-what-are-web-components)
- [Why use Web Components?](https://nhswd.com/blog/web-components-101-why-use-web-components)
- [Tutorial] How to create a Web Component? (_this post_)

## The basic rules

Even Web Components have basic rules and if we play by them, the possibilities are endless! We can even include emojis or non-Latin characters into the names, like `<animal-😺>` and `<char-ッ>`.

These are the rules:

1. You can't register a **Custom Element** more than once.
2. Custom Elements cannot be <a href="https://dev.w3.org/html5/html-author/#start-tag" title="What are self closing HTML tags?" target="_blank" rel="noopener noreferrer">self-closing</a>.
3. To prevent name clashing with existing HTML elements, <a href="https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name" title="Naming conventions for custom elements" target="_blank" rel="noopener noreferrer">valid names</a> should:
   - Always include a hyphen (-) in its name.
   - Always be lower case.
   - Not contain any uppercase characters.

## Setting up our development environment

For this tutorial, we're going to use the Components IDE from the good folks at WebComponents.dev. No set up required! Everything is already in place and properly configured, so we can start developing our component straight away. It even comes with <a href="https://storybook.js.org/" title="Storybook: Open source UI tool for building UI components in isolation" target="_blank" rel="noopener noreferrer">Storybook</a> and <a href="https://mochajs.org/" title="Mocha is a BDD Testing Framework" target="_blank" rel="noopener noreferrer">Mocha</a> preinstalled and preconfigured.

### Steps to set up our dev env

1. Go to the <a href="https://webcomponents.dev/edit/VGwFgdGkk1ayG8xf5p7h" title="Link to the Components IDE" target="_blank" rel="noopener noreferrer">Components IDE</a>
2. Click the _Fork_ button in the top right of the screen to create your copy.
3. Profit! Your environment is set up successfully.

## Defining a new HTML Element

Let's have a look at `index.html`.

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    highlight-next-line
    <script src="./dist/name-tag.js" type="module"></script>
  </head>

  <body>
    <h3>Hello World</h3>
    highlight-next-line
    <name-tag></name-tag>
  </body>
</html>
```

On line 5, we include our component with a `<script>`. This allows us to use our component, just like any other HTML elements in the `<body>` (line 10) 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 <a href="https://html.spec.whatwg.org/multipage/dom.html#htmlelement" title="HTML Spec on base HTMLElement class" target="_blank" rel="noopener noreferrer">base HTMLElement class</a>.
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 <a href="https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-api" title="HTML Spec on Custom Elements Registry" target="_blank" rel="noopener noreferrer">Custom Elements Registry</a> to teach the browser about our new component.

This is how our class should look like:

```js
class UserCard extends HTMLElement {
  constructor() {
    super();
  }
}

customElements.define('name-tag', UserCard);
```

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

## Passing values to the component with HTML attributes

Our name tag doesn't do anything interesting yet. Let's change that and display the user's name, that we pass to the component with a `name` attribute.

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

First, we have to add a `name` attribute to the `<name-tag>` in _index.html_. This enables us to pass and read the value from our component

```html
<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 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute" title="MDN Reference to the Element.getAttribute method" target="_blank" rel="noopener noreferrer">Element.getAttribute() method</a> that we add to the components `constructor()`.

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

This is how our components class should look like:

```js
class UserCard extends HTMLElement {
  constructor() {
    super();

    this.innerHTML = `<h3>${this.getAttribute('name')}</h3>`;
  }
}
...
```

Our component now outputs "John Doe".

### Add global styling

Let's add some global styling to see what happens.

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

```html
<style>
  h3 {
    color: rebeccapurple;
  }
</style>
```

## Create and use the Shadow DOM

Now it's time to get the Shadow DOM involved! This ensures the encapsulation of our element and prevents CSS and JavaScript from leaking in and out.

1. Add `this.attachShadow({mode: 'open'});` to the component's constructor ([read more about Shadow DOM modes here](https://nhswd.com/blog/web-components-101-what-are-web-components/#3-shadow-dom)).
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_:

```diff
...
constructor() {
  super();
  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

The next step is to create and use [HTML Templates](https://nhswd.com/blog/web-components-101-what-are-web-components/#2-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 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement" title="MDN Reference to the Document.createElement() method" target="_blank" rel="noopener noreferrer">Document.createElement() method</a> and assign it to our const.

```js
const template = document.createElement('template');
template.innerHTML = `
  <style>
    h3 {
      color: darkolivegreen; //because I LOVE olives
    }
  </style>

  <div class="name-tag">
    <h3></h3>
  </div>
`;
```

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.

```diff
...

class UserCard extends HTMLElement {
  constructor(){
    super();
    this.attachShadow({mode: 'open'});

-   this.shadowRoot.innerHTML = `<h3>${this.getAttribute('name')}</h3>`;
+   this.shadowRoot.appendChild(template.content.cloneNode(true));
  }
}
...

```

### What about passing attributes?!

Although we've added some styles, we see a blank page again. Our attribute values aren't rendered, so let's change that.

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

```html
<div class="name-tag">
  <h3>${this.getAttribute('name')}</h3>
</div>
```

<small>This won't work since we don't have access to the component's scope in the template.</small>

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.

```js
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

Update the `<style>` in the template to make our component look a bit more appealing:

```css
.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

The game of Web Components has to be played by a handful of basic rules but when played right, the possibilities are endless! Today, we've learned step by step how to create a simple, `<name-tag>` component by defining Custom Elements, passing HTML attributes, connecting the Shadow DOM, defining and cloning HTML templates, and some basic styling with CSS.

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