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:

Create, register and use shortcodes in WordPress

Create, register and use shortcodes in WordPress

by Gav 18/03/2023

Learn how to create and register your own WordPress shortcodes to add dynamic content to your posts and pages.

How to use guard clauses in JavaScript

How to/why use guard clauses in JavaScript

by Gav 16/03/2023

Learn how to improve code readability and performance by using guard clauses in JavaScript. Discover their benefits and best practices.

Implements and Extends, Object Oriented TypeScript

Implements and Extends, Object Oriented TypeScript

by Gav 15/03/2023

Learn the difference between implements and extends in TypeScript. Use Implements to implement interfaces and types, and extends to inherit from classes.

Reading/Parsing and Writing YAML files in PHP, Symfony

Reading/Parsing and Writing YAML files, PHP Symfony

by Gav 14/03/2023

In this tutorial we will look at using YAML in PHP. Learn about Parsing and Writing YAML files using Symfony's YAML component.

Measuring code execution performance in JavaScript

Measuring code execution performance in JavaScript

by Gav 13/03/2023

Measuring code execution performance is an important way to identify bottlenecks. Use these methods in JavaScript to help optimise your code.

Measuring script/code execution time in PHP using microtime

Measuring script/code execution time in PHP, microtime

by Gav 06/03/2023

Find bottlenecks, optimise and clean your code, and speed up your apps by measuring the execution time of your PHP scripts using microtime.

Regenerate WordPress media image sizes, programmatically

Regenerate WordPress media image sizes, programmatically

by Gav 25/02/2021

Learn how to regenerate and update WordPress media and image sizes both programmatically (without plugin), and also with a handy plugin.

Magic Constants in PHP. What they are and how to use them.

Magic Constants in PHP. What they are and how to use them

by Gav 15/02/2021

Ever seen constants like __DIR__ and __FILE__ being used in PHP? These are 'Magic Constants', and this is how we can use them.

Detecting Keypress JavaScript

Detect single and multiple keypress events: JavaScript

by Gav 16/10/2019

Learn how to use event listeners to detect and handle single and multiple keypress events in JavaScript. Add modifier keys to your application!