How To Build Custom, Reusable Web Components From Scratch
Tags: javascript, webdev, html, programming
Published: 2023-08-30 12:31:06

In HTML, there are so many elements we can use to mark up our web page. But do you know that you can also create your own custom web component?

The biggest benefit of creating your own custom web components is that you get to define its own built-in styling and functionality. In addition, you can easily reuse it at various places on your web page.

This article talks about the basics of creating a web component. By the end of this article, you’ll have all the information you need to start creating your own web component from scratch.

> Sidenote: If you’re new to learning web development, and you’re looking for the best resource to help with that, I strongly recommend [HTML to React: The Ultimate Guide](https://gumroad.com/a/834147443/dvfyN).

## Setting up the web page

Create a file named **index.html** in an empty directory. Then open it with a file editor and paste in the following code:

```xml
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="main.css">
    <script type="module" src="main.js"></script>
    <script type="module" src="bigbang.js"></script>
</head>
<body>
    <header>
        <h1>Basic Web Component</h1>
    </header>
    <main>
        <h2>Main area</h2>
        <h3>Web component below</h3>
    </main>
</body>
</html>
```

This is a web page with a header section and a main section, with each section housing different levels of title headings.

Each one of these tags has a semantic meaning behind it. In other words, they clearly describe their meaning to both the browser and the developer.

These elements all have built-in functionality and styling associated with them by the browser.

Web components, on the other hand, are your very own HTML elements. When you’re creating a custom element, you have to define how you want to element to look and behave.

Let’s see how.

## Creating a basic web component

If you check the section of your HTML markup in **index.html**, you can see that we’re importing a JavaScript module named **bigbang.js**.

This file is going to contain all the code responsible for creating our custom web component/element.

Now open the file in a text editor and paste in the following code:

```javascript
class BigBang extends HTMLElement {
    constructor() {
        super();
    }    
}

window.customElements.define('big-bang', BigBang)
```

Here defining a class that extends the `HTMLElement` class. Inside this class is where you’ll place all the code responsible for building the element. On the last line, we passed this class as the 2nd argument to the **define()** method of **customElements**, with the first argument being the tag name of the element.

This means that you could now use the custom element in HTML like this:

```xml
<big-bang></big-bang>
```

Keep in mind that there has to be a hyphen in your web component’s tag name. This helps to make it stand out as a custom component.

Now, inside the constructor method of the BigBang class, one of the things that you’ll need is a shadow root. This is like a container that’s going to hold your component:

```javascript
class BigBang extends HTMLElement {
    constructor() {
        super();
        const shadowRoot = this.attachShadow({mode: closed})
    }    
}
```

Because custom components can contain styling, scripts, and functionality, we don’t want those things spilling out and impacting the rest of the page. In other words, we don’t want to define CSS here and have it change the way that the rest of the page looks.

The other way is fine, though. The rest of the page can have its styles cascade down into our component.

The shadow root creates some sort of sandbox around our element. The ‘open’ value means that the parent script (i.e. main.js) can actually do things like **document.querySelector()**, get inside of our custom component, and see what the actual tags we’re using inside it are.

If it’s closed, they can’t.

Next, inside the constructor, create a **div** element with some text in it, then attach the **div** to the shadow root:

```javascript
class BigBang extends HTMLElement {
    constructor() {
        super();
        const shadowRoot = this.attachShadow({mode: 'closed'})
        const div = document.createElement('div')
        div.textContent = 'Big Bang Theory'
        shadowRoot.append(div)
    }        
}
```

With this code, you have just created a custom element.

To use the element in your markup, go into **index.html** and add the following element inside the **H2** tag and under the **main** tag:

```xml
<big-bang></big-bang>
```

If you open the **index.html** document in a web browser, you’ll find the custom element rendered on the page (which renders the text, ‘Big Bang Theory’):


![Screenshot of custom web component](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8bcj5p27dky1ep08cj1x.png)

The component also has text built into it, so you don’t have to pass anything between opening and closing tags as you would with most other HTML elements.

This leads to the next question: How do we get things nested inside our custom web element? The answer is slots!

## Introducing Slots

Slots allow you to “slot” content into your custom element by nesting it within the opening and closing tags of your element in HTML. Let’s see a bit of a demonstration.

At the top of the **bigbang.js** file, let’s first create a template element:

```javascript
const template = document.createElement('template')
template.innerHTML = `
    <div>
        <h2>Big Bang Theory</h2>
    </div>
`
```

Then inside the constructor, replace the entire code for creating and inserting the **div** element with that of the template:

```javascript
class BigBang extends HTMLElement {
    constructor() {
        super();
        const shadowRoot = this.attachShadow({mode: 'closed'})

        /* 
           Remove this:
           const div = document.createElement('div')
           div.textContent = 'Big Bang Theory'
           shadowRoot.append(div)
        */

        let clone = template.content.cloneNode(true)
        shadowRoot.append(clone)
    }        
}
```

We basically cloned everything in the template and appended it to the shadow root. If you check your browser, you should find the text appearing on the page as an **H2**.

Now is the time to slot in some actual content. Inside the markup in **index.html**, put the following content between the **&lt;big-bang&gt;** tags:

```javascript
<big-bang>
  <h2 slot="title">Characters</h2>
  <ul slot="list">
    <li>Leonard</li>
    <li>Kingsley</li>
    <li>Sarah</li>
    <li>John</li>
  </ul>
</big-bang>
```

If you want to be able to use nested content, you need to give them a slot name as we did above.

Now back in your template, in order to include these elements inside the template, you need to add the tag for each of them:

```javascript
const template = document.createElement('template')
template.innerHTML = `
    <div>
        <h1>Big Bang Theory</h1>
        <slot name="title">Default text if no title slot</slot>
        <slot name="list"></slot>
    </div>
`
```

We also added a default title in case none is provided in the HTML. This is the result (slots highlighted in red):


![Screenshot of web component with slot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rt4ygw21rv5oezzudyxi.png)

## Conclusion

That’s all! Just with HTML and a few lines of JavaScript, you were able to create your very own web component.

Now there is more to this. We can define styles, behaviors, and functionality. I’ll be tackling each of these in the coming articles. So make sure to subscribe so you don’t miss them.

Want to collaborate with me? [Fill out this form](https://forms.gle/2h3UKhM55KZPNXm5A).