Sign Up
Hero

Web Components: How To Craft Your Own Custom Components

Learn how to make web components and leverage them in your applications today.


TL;DR The introduction of Web Components have given developers super powers. With Web Components, web designers and developers are no longer limited to the existing HTML tags that existing browser vendors provide. Developers now have the ability to create new custom HTML tags, enhance existing HTML tags or extend components that developers around the world have created. In this article, I'll show you how to use and create custom web components for your apps.


Web components allow for reusability and the ability to associate JS behaviour with your markup. Developers can search for existing components created by other developers on the web components registry. In the absence of suitable existing custom elements, developers can create theirs and make it available for others by publishing it to the registry.

What are Web Components?

Web components are a set of web platform APIs that allow you to create new custom, reusable, encapsulated HTML tags to use in web pages and web apps. They are reusable widgets that are built on the Web Component standards. Web Components work across modern browsers and can be used with any JavaScript library or framework that utilizes HTML.

There are some sets of rules and specifications that you need to follow to develop web components. These specifications are classified into four categories:

  • Custom Elements
  • Shadow DOM
  • HTML Imports
  • HTML Template

We'll talk about these specifications in the latter part of this post. But let's quickly learn how to use web components.

How to use Web Components

The first step is to browse the element registry. Check for the components that you are interested in, then go through the README to know how to import it and use in your web applications.

The web component registry has two main sections:

An example web component you can install is juicy-ace-editor. You can install it by following these processes:

Make sure you have bower installed, else run:

npm install -g bower

Now install the ace-editor component like so:

bower install juicy-ace-editor --save

Create an index.html file and import the juicy-ace-editor component like this:

<link rel="import" href="bower_components/juicy-ace-editor/juicy-ace-editor.html">

and place the component on the page like this:

 <juicy-ace-editor theme="ace/theme/monokai" mode="ace/mode/javascript"></juicy-ace-editor>

This is an example of the component in the index.html file.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
    <link rel="import" href="bower_components/juicy-ace-editor/juicy-ace-editor.html">
    <style type="text/css">
      #editor-container {
        position: absolute;
        top:  0px;
        left: 280px;
        bottom: 0px;
        right: 0px;
        background: white;
      }
    </style>
</head>
<body>
    <juicy-ace-editor id="editor-container" theme="ace/theme/monokai" mode="ace/mode/javascript">
    var User          = require('./controllers/user.server.controller'),
        Notification  = require('./controllers/notification.server.controller');

        module.exports = function(app) {

          app.get('/api',  User.welcome);

          app.post('/api/users',           User.createNewUser);
          app.delete('/api/user/:user_id', User.deleteOneUser);

          app.post('/api/notify', Notification.notifyUsers);
        };
    </juicy-ace-editor>
</body>
</html>

In the code above, we referenced a script:

    <script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>

The Webcomponentjs file is the Web components' polyfill for browsers that don't support web components yet.

When you check out your browser, this is how your page will look like:

Follow the documentation here to install and run it in your web browser.

It is that simple. Now we have a code editor in our browser by just importing a Web Component. Whoop! Whoop!

Now, let's go through the Web Components specifications in order to know how to create a custom component, starting from Custom Elements.

How to create Web Components

Custom Elements

This is a web component specification that defines how to craft and use new types of DOM elements. There are some ground rules on how to name and define your custom elements. They are:

  • The name of your custom element must contain a dash (-). For example, <file-reader>, and <skype-login> are valid names for custom elements, while <skype_login>, and <skypelogin> are not. This is necessary in order to allow the HTML parser differentiate between a custom element and an inbuilt HTML element.

  • A custom element can't be registered more than once. A DOMException error will be thrown if you do so.
  • A custom element can't be self-closing. For example, you can't write a custom element like this: <skype-login />. It should always be written like this: <skype-login></skype-login>.

A custom element can be created using the customElements.define() browser API method and a class that extends HTMLElement in JavaScript like so:

class FileBag extends HTMLElement {
  // Define behavior here
}

window.customElements.define('file-bag', FileBag);

Another option is to use an anonymous class like so:

window.customElements.define('file-bag', class extends HTMLElement {
  // Define behaviour here
});

With this already defined, you can now use the custom element in a web page like so:

  <file-bag></file-bag>

You can define properties on a customElement. For instance, let's add an attribute called open to our <file-bag> element. This can be achieved like so:

class FileBag extends HTMLElement {
  // Set the "open" property
  set open(option) {
    this.setAttribute("open", option);
  }

  // Get the "open" property
  get open() {
    return this.hasAttribute("open");
  }

}

this refers to the DOM element itself. So in this example, this refers to <file-bag>.

Once you have done this, you can now use the custom element in your browser like this:

  <file-bag open="true"></file-bag>

Note: You can also define a constructor in the class, but you have to call the super() method just before adding any other piece of code.

There are lifecycle hooks that custom elements can define during their existence. These hooks are:

  • constructor(): Here, you can attach event listeners and initialize state.
  • connectedCallback(): Called whenever the custom element is inserted into the DOM.
  • disconnectedCallback(): Called whenever the custom element is removed from the DOM.
  • attributeChangedCallback(attrName, oldVal, newVal): Called whenever an attribute is added, removed or updated. Only attributes listed in the observedAttributes property are affected.
  • adoptedCallback(): Called whenever the custom element has been moved into a new document.

You can reference the custom element specification for a lot more information.

Shadow DOM

This is a powerful API to combine with custom elements. It provides encapsulation by hiding DOM subtrees under shadow roots. You can use Shadow DOM in a custom element like so:

window.customElements.define('file-bag', class extends HTMLElement {
  constructor() {
    super();
    var shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = `<strong>Shadow dom super powers for the win!</strong>`;
  }
});

So when you call <file-bag><p>This is a file bag </p></file-bag> in the browser, it will be rendered like so:

  <file-bag>
    <strong>Shadow dom super powers for the win!</strong>
  </file-bag>

The main idea behind Shadow DOM is to mask all of the markup behind a custom element in the shadows. If you inspect the element in the browser, you won't see any of the markup apart from the attributes of the element. They are hidden under shadow roots. Browser vendors have been using Shadow DOM for years to natively implement elements such as <input>, <audio>, <video> and many others. Another benefit is that all the styling and scripts inside the custom element won't accidentally leak out and affect anything else on the page.

You can reference the shadow DOM specification for a lot more information.

HTML Imports

HTML Imports are a way to include and reuse HTML documents in other HTML documents. The import keyword is assigned to the rel attribute of the link element like so:

<link rel="import" href="/imports/file-reader.html">

You can reference the HTML Imports for a lot more information.

HTML Template

This is a web component specification that defines how to declare pieces of markup at page load.

The <template> tag is placed within the web component. You can write HTML and CSS code within this tag to define how you want the component to be presented in the browser.

You can reference the HTML Template specification for a very detailed information on templating.

Build a Vimeo Embed Web Component

We'll build a web component that will allow users embed vimeo videos into their apps easily. Let's get started.

Create a new HTML file, video-embed.html. Define the HTML Template markup like so:

<!-- Defines element markup -->
<template>
    <style>
        .vimeo {
            background-color: #000;
            margin-bottom: 30px;
            position: relative;
            padding-top: 56.25%;
            overflow: hidden;
            cursor: pointer;
        }
        .vimeo img {
            width: 100%;
            top: -16.82%;
            left: 0;
            opacity: 0.7;
        }
        .vimeo .play-button {
            width: 90px;
            height: 60px;
            background-color: #333;
            box-shadow: 0 0 30px rgba( 0,0,0,0.6 );
            z-index: 1;
            opacity: 0.8;
            border-radius: 6px;
        }
        .vimeo .play-button:before {
            content: "";
            border-style: solid;
            border-width: 15px 0 15px 26.0px;
            border-color: transparent transparent transparent #fff;
        }
        .vimeo img,
        .vimeo .play-button {
            cursor: pointer;
        }
        .vimeo img,
        .vimeo iframe,
        .vimeo .play-button,
        .vimeo .play-button:before {
            position: absolute;
        }
        .vimeo .play-button,
        .vimeo .play-button:before {
            top: 50%;
            left: 50%;
            transform: translate3d( -50%, -50%, 0 );
        }
        .vimeo iframe {
            height: 100%;
            width: 100%;
            top: 0;
            left: 0;
        }
    </style>
    <div class="vimeo">
        <div class="play-button"></div>
    </div>
</template>

We have also added CSS style to the template tag to define the styling of the vimeo-embed component.

The next step is to actually create the custom element. Now add a <script> tag just after the <template> tag and create it like so:

<script>
(function(window, document, undefined) {
    // Refers to the "importer", which is index.html
    var thatDoc = document;
    // Refers to the "importee", which is vimeo-embed.html
    var thisDoc = (thatDoc._currentScript || thatDoc.currentScript).ownerDocument;
    // Gets content from <template>.
    var template = thisDoc.querySelector( 'template' ).content;
    // Shim Shadow DOM styles if needed
    if (window.ShadowDOMPolyfill) {
        WebComponents.ShadowCSS.shimStyling(template, 'vimeo');
    }
    class VimeoEmbed extends HTMLElement {
        constructor() {
            super();
        }
        connectedCallback() {
            var shadowRoot = this.attachShadow({mode:'open'});
            // Adds a template clone into shadow root.
            var clone = thatDoc.importNode( template, true );
            shadowRoot.appendChild(clone);
            // get the value of the "embed" attribute
            var embed = this.getAttribute("embed");
            var video = shadowRoot.querySelector( ".vimeo" );
            this.createAndPlay(embed, video);
        }
        createAndPlay(embedID, videoElem) {
            videoElem.addEventListener( "click", function() {
                var iframe = document.createElement( "iframe" );
                iframe.setAttribute( "frameborder", "0" );
                iframe.setAttribute( "allowfullscreen", "" );
                iframe.setAttribute( "webkitallowfullscreen", "" );
                iframe.setAttribute( "mozallowfullscreen", "" );
                iframe.setAttribute( "src", "https://player.vimeo.com/video/" + embedID + "?autoplay=1" );
                iframe.setAttribute( "width", "640");
                iframe.setAttribute( "height", "360");
                this.innerHTML = "";
                this.appendChild( iframe );
            });
        }
    }
    window.customElements.define('vimeo-embed', VimeoEmbed);
})(window, document);
</script>

We have the constructor, connectedCallback and createAndPlay method. In the constructor, we called the super() method to have access to the methods and properties of HTMLElement.

The connectedCallback method is a lifecycle hook that our custom element provides. So, we have deferred the work of setting up the shadow root, getting the value of the embed attribute and also calling the createAndPlay in this hook. As I mentioned earlier, this method is called whenever the custom element is inserted into the DOM.

In the createAndPlay method, we simply added a click eventlistener and used JavaScript to create an iframe and set the required attributes.

Finally we called window.customElements.define('vimeo-embed', VimeoEmbed); to attach the VimeoEmbed class to vimeo-embed custom tag.

HTML Import

Create an index.html file. Go ahead and import the vimeo-embed.html file in it like so:

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>Vimeo Embed</title>
    <script src="./bower_components/webcomponentsjs/webcomponents.min.js"></script>
    <link rel="import" href="vimeo-embed.html">
    <style type="text/css">
        .wrapper {
            max-width: 680px;
            margin: 60px auto 100px;
        }
    </style>
</head>
<body>
    <div class="wrapper">
        <vimeo-embed embed="203909195"></vimeo-embed>
    </div>
</body>
</html>

Oh, you can see the webcomponentsjs polyfill referenced in the script tag. How did we get that?

Install it via bower like this:

bower install webcomponentsjs --save

Browser View

From your terminal, run a local server, e.g http-server to serve up the web page.

Your web page should display the component like so:

Load Web Component

Once you click the play button, the video should autoplay:

Video should autoplay

Inspect the page with Chrome DevTools, check out the <video-embed> tag:

video embed tag

Check out the Shadow DOM below:

Shadow Dom

Now that we have a fully functional vimeo embed web component, let's package it and submit to the registry.

Submit To The Web Component Registry

There is a list of requirements to adhere to before submitting your component to the registry. Follow the instructions below:

Go ahead and publish

Now, your component should be visible in the registry.

Yaay!

Browser Support for Web Components

Google Chrome is leading the pack of browsers with stable support for Web Components in their web and mobile browsers. Take a look at the browser support matrix below:

Source: webcomponentjs

To be safe, it is recommended to use webcomponentsjs, to provide support for many browsers.

We used webcomponentsjs during the course of building our own custom element. webcomponentsjs is a suite of polyfills supporting the Web Components. These polyfills are intended to work in the latest version of browsers.

Note: Web Components capabilities are disabled by default in Firefox. To enable them, go to the about:config page and dismiss any warning that appears. Then search for the preference called dom.webcomponents.enabled, and set it to true.

Tools for Building Web Components

There are libraries available that make it easier to build web components. Some of these libaries are:

Aside: Auth0 Authentication with JavaScript

At Auth0, we make heavy use of full-stack JavaScript to help our customers to manage user identities, including password resets, creating, provisioning, blocking, and deleting users. Therefore, it must come as no surprise that using our identity management platform on JavaScript web apps is a piece of cake.

Auth0 offers a free tier to get started with modern authentication. Check it out, or sign up for a free Auth0 account here!

Then, go to the Applications section of the Auth0 Dashboard and click on "Create Application". On the dialog shown, set the name of your application and select Single Page Web Applications as the application type:

After the application has been created, click on "Settings" and take note of the domain and client id assigned to your application. In addition, set the Allowed Callback URLs and Allowed Logout URLs fields to the URL of the page that will handle login and logout responses from Auth0. In the current example, the URL of the page that will contain the code you are going to write (e.g. http://localhost:8080).

Now, in your JavaScript project, install the auth0-spa-js library like so:

npm install @auth0/auth0-spa-js

Then, implement the following in your JavaScript app:

import createAuth0Client from '@auth0/auth0-spa-js';

let auth0Client;

async function createClient() {
  return await createAuth0Client({
    domain: 'YOUR_DOMAIN',
    client_id: 'YOUR_CLIENT_ID',
  });
}

async function login() {
  await auth0Client.loginWithRedirect();
}

function logout() {
  auth0Client.logout();
}

async function handleRedirectCallback() {
  const isAuthenticated = await auth0Client.isAuthenticated();

  if (!isAuthenticated) {
    const query = window.location.search;
    if (query.includes('code=') && query.includes('state=')) {
      await auth0Client.handleRedirectCallback();
      window.history.replaceState({}, document.title, '/');
    }
  }

  await updateUI();
}

async function updateUI() {
  const isAuthenticated = await auth0Client.isAuthenticated();

  const btnLogin = document.getElementById('btn-login');
  const btnLogout = document.getElementById('btn-logout');

  btnLogin.addEventListener('click', login);
  btnLogout.addEventListener('click', logout);

  btnLogin.style.display = isAuthenticated ? 'none' : 'block';
  btnLogout.style.display = isAuthenticated ? 'block' : 'none';

  if (isAuthenticated) {
    const username = document.getElementById('username');
    const user = await auth0Client.getUser();

    username.innerText = user.name;
  }
}

window.addEventListener('load', async () => {
  auth0Client = await createClient();

  await handleRedirectCallback();
});

Replace the YOUR_DOMAIN and YOUR_CLIENT_ID placeholders with the actual values for the domain and client id you found in your Auth0 Dashboard.

Then, create your UI with the following markup:

<p>Welcome <span id="username"></span></p>
<button type="submit" id="btn-login">Sign In</button>
<button type="submit" id="btn-logout" style="display:none;">Sign Out</button>

Your application is ready to authenticate with Auth0!

Check out the Auth0 SPA SDK documentation to learn more about authentication and authorization with JavaScript and Auth0.

Note: If you want to use Auth0 authentication to authorize API requests, note that you'll need to use a different flow depending on your use case. Auth0 idToken should only be used on the client-side. Access tokens should be used to authorize APIs. You can read more about making API calls with Auth0 here.

Conclusion

Web components have a lot more benefits than meets the eye. Web Components allow for less code, modular code and more reuse in our apps.

In my opinion, the major selling point of Web components is reusability and simplicity of use. The more high quality components developers submit to the registry, the more a plethora of better tools will be available to the community for building better and beautiful web apps in less time!

Have you been using Web Components for a while? Do you think Web Components are the future for web app development? Are they just another hipster technology? I'll like to know your thoughts in the comment section.