introduction to Web Components

"Web Components is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps."MDN web docs

Some browsers are still in the process of updating to support the standards for Web Components. In the mean time, polyfills simulate the missing browser capabilities as closely as possible.

You can feature-detect for the necessary browser features before loading the polyfills. This means that as more and more browsers implement the Web Components standards, the payload to run your apps and elements will decrease. The polyfills are available on GitHub.

Web Components consists of three main technologies, in particular custom elements, shadow DOM and HTML templates.

custom elements

"A set of JavaScript APIs that allow you to define custom elements and their behaviour, which can then be used as desired in your user interface."MDN web docs

Custom elements are divided between autonomous custom elements and customized built-in elements. To create a custom element we need to create a JavaScript class and register the class as custom element in the CustomElementsRegistry. To use the created element we just have to include the JavaScript file and we are good to go.

simple autonomous custom element

Autonomous custom elements require a dash (-) in its name to ensure forward compatibility, in case the browser will add new elements to their collection in the future. e.g.: simple-element. Let's start with a super simple autonomous custom element.

See the Pen simple autonomous custom element by Samuel Weber (@AndTheGodsMadeLove) on CodePen.

This element doesn't utilize shadow DOM, templates or has any functionality appended. So it really doesn't make any sense other then we can see how to create and register it.

simple customized build-in element

Instead of extending the HTMLElement interface, customized built-in elements extend their specific interface. e.g.: HTMLButtonElement

To turn an element into the customized one you need to add the is attribute to it. Let's create and use another useless element for the sake of consistency.

See the Pen simple customized build-in element by Samuel Weber (@AndTheGodsMadeLove) on CodePen.

Of course this example makes no sense as well. Before we dig into shadow DOM and templates we will take a look at something called lifecycle callbacks:

lifecycle callbacks

Custom elements have four instance methods which are called at different times. In particular connectedCallback, attributeChangedCallback, adoptedCallback and disconnectedCallback.

connectedCallback() is called after the custom element is attached to the DOM inside the HTML or with JavaScript's appendChild(). The custom element has to be defined already otherwise the callback will fire when its state gets upgraded from unknown to defined.

See the Pen custom element lifecycle - connectedCallback() by Samuel Weber (@AndTheGodsMadeLove) on CodePen.

There is a discussion about the difference of the constructor() and the connectedCallback() on stackoverflow. The answer includes a nice code snippet to test the behavior of those methods and gives some useful insight about the upgrade process from and unknown element to a defined custom element. Check it out!

attributeChangedCallback(element, oldValue, newValue) is called after specific attribute of the custom element are changed. The callback comes with three arguments: element, oldValue, newValue. By default the Browser will not observe any attributes, we have to define a list of attributes we want to observe in a static property called observedAttributes.

See the Pen custom element lifecycle - attributeChangedCallback() by Samuel Weber (@AndTheGodsMadeLove) on CodePen.

As we can see the attributeChangedCallback() invokes even if we attach the element inside HTML.

adoptedCallback() runs when you call adoptNode on a custom element that's inside another document or document fragment. This method was useful while the HTML Imports spec was a thing. Since they stopped the development of this spec there is almost no use for this callback. If you find one let me know it in the comments <3

disconnectedCallback() runs when you remove an element from the DOM:

See the Pen custom element lifecycle - disconnectedCallback() by Samuel Weber (@AndTheGodsMadeLove) on CodePen.

Pretty straight forward. Let's move on to the HTML templates and how to utilize this concept inside the custom elements.

HTML templates

When you have to reuse the same markup structures repeatedly on a web page, it makes sense to use some kind of a template rather than repeating the same structure over and over again. This was possible before, but it is made a lot easier by the HTML <template> element (which is well-supported in modern browsers). This element and its contents are not rendered in the DOM, but it can still be referenced using JavaScript. MDN web docs

HTML templates in combination with Web Components brings a great performance boost since cloning a template is a lot faster then creating the markup with document.create() or .innerHTML.

Following codepen will showcase how to clone templates:

See the Pen clone templates by Samuel Weber (@AndTheGodsMadeLove) on CodePen.

I recently tested the difference between cloneNode(), createElement() and .innerHTML and the outcome was pretty surprising. .innerHTML is super slow compared to the other two methods, up to 500 times slower! cloneNode() and createElement() are almost the same in modern browsers, in Edge and older browsers cloneNode() wins the race. But following really surprised me: In modern browser and for simple templates (small templates with only 2-3 elements) createElement() is faster then cloneNode(). Of course this is super rare because you generally want to provide your product to as many browsers as possible and templates usually are more complex.

Alright, the concept of HTML elements is pretty easy. Let's go on and take a look at the shadow DOM:

shadow DOM

"An important aspect of web components is encapsulation — being able to keep the markup structure, style, and behavior hidden and separate from other code on the page so that different parts do not clash, and the code can be kept nice and clean. The Shadow DOM API is a key part of this, providing a way to attach a hidden separated DOM to an element." MDN web docs

The shadow DOM can be attached to any element and provides a totally encapsulated document fragment. Following codepen shows how to append a shadow DOM to a standard element:

See the Pen shadow DOM by Samuel Weber (@AndTheGodsMadeLove) on CodePen.

As you can see the paragraph element inside the shadow DOM isn't effected by the CSS declaration and is fully encapsulated.

open vs closed mode

attachShadow() adds the shadow DOM to the element. The mode property can be open or closed. With mode open you can access the shadow DOM via the shadowRoot property of the element. While with mode closed the shadowRoot property is null. However, you can store the reference of the shadow DOM given by attachShadow() to gain access.

const opened = document.createElement('div');
opened.attachShadow({mode: 'open'});
console.log(opened.shadowRoot);  // #shadow-root (open);

const closed = document.createElement('div');
const closedShadowRoot = closed.attachShadow({mode: 'closed'});
console.log(closed.shadowRoot); // null
console.log(closedShadowRoot); // #shadow-root (closed)

closed mode provides some extra layer of encapsulation as the developer can decide how to access the shadow DOM. Still, there is no real way to prevent access from outside. Because in the end we are talking about JavaScript ;) Check out this interesting article about open vs closed with further details on how to "pierce" closed mode from the outside of a web component.

custom element with shadow dom

Before we jump into the codepen for the custom element that utilize shadow DOM, I want to mention it would be fine to attach the elements to the shadowRoot inside the constructor(). But if you are going to use polyfills, I recommend you attach elements inside the connectedCallback() to the shadowDOM, since polyfills will add classes to the light DOM. And the connectedCallback() ensures that the element is attached to the light DOM.

See the Pen basic web component utilizing shadow DOM by Samuel Weber (@AndTheGodsMadeLove) on CodePen.

basic styling

So if everything inside the shadow DOM is totaly encapsulated, how can we style it? Simply add a style element to the template which gets appended to the shadow DOM:

See the Pen basic styling of a web component by Samuel Weber (@AndTheGodsMadeLove) on CodePen.

slot elements and selectors

"The HTML <slot> element – part of the Web Components technology suite – is a placeholder inside a web component that you can fill with your own markup, which lets you create separate DOM trees and present them together." MDN web docs

Let's take a look at a very simple example before we hop into named slot elements and the related CSS selector:

See the Pen slot element and the ::slotted() selector - example 1 by Samuel Weber (@AndTheGodsMadeLove) on CodePen.

In the above example we attached the custom element twice. One without a text node and one with a text node. The <slot> element inside the template contains the default value in case the custom element have no text node.

The next example contains two attached elements and named slot elements inside the template:

See the Pen slot element and the ::slotted() selector - example 2 by Samuel Weber (@AndTheGodsMadeLove) on CodePen.

The ::slotted() CSS selector can be used to style filled <slot> elements:

See the Pen slot element and the ::slotted() selector - example 1 by Samuel Weber (@AndTheGodsMadeLove) on CodePen.

The syntax for the ::slotted() CSS selector can be found here. It's also worth to mention that the default value of a <slot> element is inside the shadow DOM and a filled <slot> element will be placed inside the light DOM and can be styled from the outside. The ::slotted() CSS selector will be overwritten by the style of the document!

See the Pen slot element and the ::slotted() selector - example 4 by Samuel Weber (@AndTheGodsMadeLove) on CodePen.

:host selector

The :host selector can be used to style the Web Component itself. The :host will be overwritten by styles from the document as well.

:host without CSS inside the document:

See the Pen :host selector - example 1 by Samuel Weber (@AndTheGodsMadeLove) on CodePen.

:host with CSS inside the document:

See the Pen :host selector - example 2 by Samuel Weber (@AndTheGodsMadeLove) on CodePen.

That's it for today. If you have any questions or want another topic of Web Components highlighted, let me know in the comment section and will respond ASAP.

With best regards,

comment successfully committed

leave a comment:

please enter your first name above
please enter a valid email above
please enter your message before you send the form


please enter your name
please enter a valid email above
please enter the subject for your request
please enter your message before you send the form
mail successfully committed