Creating Web Components

  • Last updated on 24th Feb 2023

Introduction

In this article, we’ll be building our own custom web components or HTML element. Web Components help us create standardized, modular, and reusable building blocks for our web applications.

Importing custom web component

In our HTML we can import it like so:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Web Components</title>
    <!-- Our own custom element import below -->
    <script src="./monsters.js" type="module"></script>
  </head>
  <body>
    <header>
      <h1>Basic Web Components</h1>
    </header>
    <main>
      <h2>Our Web Component Below</h2>
      <!-- web component -->
      <monster-encounters> ... </monster-encounters>
    </main>
  </body>
</html>

Defining custom web components & shadow DOM

In our JS, we don’t export the custom web component. We only need to run the below.

class Monsters extends HTMLElement {
  constructor() {
    super()
    const shadowRoot = this.attachShadow({ mode: 'closed' })
    let div = document.createElement('div')
    div.textContent = 'You have encoutered monsters!'
    shadowRoot.append(div)
  }
}

// The first parameter of our define method has to have a hyphen
customElements.define('monster-encounters', Monsters)

The shadowRoot variable holds the web component. The attachShadow method that takes in an object option and is like a sandbox container. This is what we call the shadowDOM. It serves to encapsulate and we can add css to it in particular. Below is a visual:

img

We are saying that our CSS styles applied to our custom component should not impact native normal elements of our HTML like div, a, and ul tags and so on, for example.

It is worth noting that some styles defined globally will be inherited (and cascade down) by our monsters-encounters component, like our color, font-size, font-weight, etc.

Using the mode: ‘closed’ argument just means that our parent (the DOM) can’t change or select (for example, document.query()) elements inside our custom web component.

<monster-encounters> Adding text here is a no-go! </monster-encounters>

If we we’re to modify the html to include content inside our web component, the above wouldn’t work because only our Monsters class can define the content inside.

Instead our html will show:

img

Working w/ slots to template (nest) content in our custom web component

If we want to include a list of monsters, we have to use the slot attribute inside the elements we want to nest in our custom web component.

...
<monster-encounters>
  <h3 slot="title">Monster Types</h3>
  <ul slot="list">
    <li>Nymph</li>
    <li>Bear</li>
    <li>Stone Golem</li>
    <li>Air Elemental</li>
  </ul>
</monster-encounters>
...

Above, we’re defining a slot attribute with a value of title and another with a value of list. Both have to be defined in our web component template used in our class.

Then, to include the changes in our template we:

const template = document.createElement('template')
template.innerHTML = `
  <div>
    <h3>Monster Encounters</h3>
    <slot name="title">Default text if not title slot used in HTML</slot>
    <slot name="list"></slot>
  </div>
`

class Monsters extends HTMLElement {
  constructor() {
    super()
    const shadowRoot = this.attachShadow({ mode: 'closed' })
    let clone = template.content.cloneNode(true)
    shadowRoot.append(clone)
  }
}

customElements.define('monster-encounters', MonsterEncounters)
// <monster-encounters>

We use the template element to declare a reusable HTML fragment. This can be cloned and then added to our DOM. That’s exactly what we do inside the clone variable in our Monsters class constructor.

Above we’re creating an HTML template using the template tag. We then define a div element with a heading and two named slots - “title” and “list”.

Ending Thoughts

In this article, we learned how to declare web components and use the shadow DOM to leverage modular and reusable design. Hopefully it has sparked some ideas on how to use them. Have a great one!

Resources