Web Components tutorial: Custom Elements in JavaScript

A Web Component

I’ve been building custom element web components in JavaScript for several months now (modal, tabs, menu, sidebars, inputs, context menus, etc) and I see them as the perfect accompaniment or replacement for front-end frameworks. I thought I would write a tutorial series because I often find myself explaining to other developers what web components are.

There are already frameworks, like litElement, for helping build web components. Personally I prefer vanilla JavaScript code so I will be showing you how to make vanilla web components. As a result, when we come to polyfill we will have to take some extra care.

In this series we will start with a bit of theory and move on to implementing what we know by building a modal dialogue box. A modal is a good example to use because we will have to learn how to use a slotted shadow root, lifecycle callbacks and advanced CSS styling selectors.

  • Web Components tutorial: Custom Elements in JavaScript
  • HTML Templates in Web Components: The shadowRoot
  • Styling a Web Component: Selectors and CSS Variables
  • Web Component Lifecycle Callbacks
  • Building a Web Component: Modal tutorial
  • Web Component Polyfills: ShadyCSS and Edge

What is a web component?

A web component is basically a cocktail consisting of a Custom Element, a Shadow DOM and a HTML Template. They allow JavaScript developers to define their own HTML elements, complete with encapsulated custom behaviours, logic, structure and styles.

Within your document they look like normal HTML tags (conventionally with a hyphen) with functionality you would normally find in more complicated HTML, CSS and JavaScript structures.

    <p>An example of what a web component tag might look like in context</p>
    <button onClick="openModal()">Open</button>
        <p>Modal content</p>

Web components are supported by all major browsers and can be polyfilled for Edge. Internet Explorer does have a myriad of issues with them, but screw internet explorer, right?

So what is a Custom Element, Shadow DOM and HTML Template?

Custom Element

Simply, this is a collection of JavaScript APIs which allow developers to make their own fully-featured DOM elements. They look something like this:


Shadow DOM

Rendered separately to your main DOM, a Shadow DOM is an encapsulated element tree. Think of it a bit like a document fragment where elements, behaviours and styles are abstracted away from your main document, keeping them private – fancy scoping, if you prefer.

The light DOM is the content inside your main document.

HTML Template

I imagine everyone has come across HTML Templates. They allow developers to make ‘render-when-needed’, reusable, slotted templates to be scattered like confetti once instantiated by JavaScript.

Why use a web component?

Encapsulated, clean and reusable code. As the web has evolved, complex GUI and applications have become trickier and trickier to maintain cleanly. By building web components we can abstract our code and structure into custom HTML elements, neatly called from their own HTML tags.

I can make a web component which can be dropped into any application without the application screwing with my web component or my web component screwing with the application. Great, right?

Basic structure of a web component

Let’s look at the basic structure of a web component; a JavaScript class which extends HTMLElement:

class ClassName extends HTMLElement {
    constructor() {

        if (!this.shadowRoot) {
            this.attachShadow({mode: 'open'});

            this.shadowRoot.innerHTML = ``;
    connectedCallback() {

    static get observedAttributes() {
        return [];
    attributeChangedCallback(name, oldValue, newValue) {

    disconnectedCallback() {


customElements.define('element-name', ClassName);

The template (shadowRoot)

The shadowRoot is where we attach the HTML Template. Access to the Light DOM can be given through slot tags, but ‘most’ of the structure we place inside the shadowRoot is protected within the Shadow DOM.

A typical shadowRoot could look like this:

if (!this.shadowRoot) {
    this.attachShadow({mode: 'open'});

    this.shadowRoot.innerHTML = `
            .inner {
                background-color: green;
        <div class="inner">

In this example, a common CSS class (‘inner’) is unique inside the shadowRoot, even if used in our main document style sheet. The main document styling does not penetrate the Shadow DOM and the styling inside the shadowRoot does not leak to the main document.

Lifecycle Callbacks

A web component contains several lifecycle callbacks. These are functions inside the web component which are invoked under certain conditions, such as when an attribute is updated or the component is connected.

Connected Callback

The connectedCallback allows us to modify behaviour when a web component is first connected to the DOM. This is useful because it gives us a place to hook up actions such as event listeners.

Disconnected Callback

The opposite of the connected callback, the disconnected callback is invoked when the web component is removed from the DOM. Therefore we can do useful things, like remove event listeners, when we no longer need our component.

AttributeChangedCallback and observedAttributes

The attributeChangedCallback is a mutation observer watching for changes on the attributes of a web component. This is useful because we can change attribute values to trigger custom behaviours.

Check back in a few days for the next Web Components tutorial and to learn about HTML Templates in Web Components.

Join the discussion!

You might like: