Front-end development moves at a break-neck pace. This is made evident by the myriad articles, tutorials, and Twitter threads bemoaning the state of what once was a fairly simple tech stack. In this article, I’ll discuss why Web Components are a great tool to deliver high-quality user experiences without complicated frameworks or build steps and that don’t run the risk of becoming obsolete. In subsequent articles of this five-part series, we will dive deeper into each of the specifications.
This series assumes a basic understanding of HTML, CSS, and JavaScript. If you feel weak in one of those areas, don’t worry, building a custom element actually simplifies many complexities in front-end development.
Article Series:
- An Introduction to Web Components (This post)
- Crafting Reusable HTML Templates
- Creating a Custom Element from Scratch
- Encapsulating Style and Structure with Shadow DOM
- Advanced Tooling for Web Components
What are Web Components, anyway?
Web Components consist of three separate technologies that are used together:
- Custom Elements. Quite simply, these are fully-valid HTML elements with custom templates, behaviors and tag names (e.g.
<one-dialog>
) made with a set of JavaScript APIs. Custom Elements are defined in the HTML Living Standard specification. - Shadow DOM. Capable of isolating CSS and JavaScript, almost like an
<iframe>
. This is defined in the Living Standard DOM specification. - HTML templates. User-defined templates in HTML that aren’t rendered until called upon. The
<template>
tag is defined in the HTML Living Standard specification.
These are what make up the Web Components specification.
HTML Modules is likely to be the fourth technology in the stack, but it has yet to be implemented in any of the big four browsers. The Chrome team has announced it an intent to implement them in a future release.
Web Components are generally available in all of the major browsers with the exception of Microsoft Edge and Internet Explorer 11, but polyfills exist to fill in those gaps.
Referring to any of these as Web Components is technically accurate because the term itself is a bit overloaded. As a result, each of the technologies can be used independently or combined with any of the others. In other words, they are not mutually exclusive.
Let’s take a quick look at each of those first three. We’ll dive deeper into them in other articles in this series.
Custom elements
As the name implies, custom elements are HTML elements, like <div>
, <section>
or <article>
, but something we can name ourselves that are defined via a browser API. Custom elements are just like those standard HTML elements — names in angle brackets — except they always have a dash in them, like <news-slider>
or <bacon-cheeseburger>
. Going forward, browser vendors have committed not to create new built-in elements containing a dash in their names to prevent conflicts.
Custom elements contain their own semantics, behaviors, markup and can be shared across frameworks and browsers.
class MyComponent extends HTMLElement {
connectedCallback() {
this.innerHTML = `<h1>Hello world</h1>`;
}
}
customElements.define('my-component', MyComponent);
See the Pen
Custom elements demo by Caleb Williams (@calebdwilliams)
on CodePen.
In this example, we define <my-component>
, our very own HTML element. Admittedly, it doesn’t do much, however this is the basic building block of a custom element. All custom elements must in some way extend an HTMLElement
in order to be registered with the browser.
Custom elements exist without third-party frameworks and the browser vendors are dedicated to the continued backward compatibility of the spec, all but guaranteeing that components written according to the specifications will not suffer from breaking API changes. What’s more, these components can generally be used out-of-the-box with today’s most popular frameworks, including Angular, React, Vue, and others with minimal effort.
Shadow DOM
The shadow DOM is an encapsulated version of the DOM. This allows authors to effectively isolate DOM fragments from one another, including anything that could be used as a CSS selector and the styles associated with them. Generally, any content inside of the document’s scope is referred to as the light DOM, and anything inside a shadow root is referred to as the shadow DOM.
When using the light DOM, an element can be selected by using document.querySelector('selector')
or by targeting any element’s children by using element.querySelector('selector'
); in the same way, a shadow root’s children can be targeted by calling shadowRoot.querySelector
where shadowRoot
is a reference to the document fragment — the difference being that the shadow root’s children will not be select-able from the light DOM. For example, If we have a shadow root with a <button>
inside of it, calling shadowRoot.querySelector('button')
would return our button, but no invocation of the document’s query selector will return that element because it belongs to a different DocumentOrShadowRoot
instance. Style selectors work in the same way.
In this respect, the shadow DOM works sort of like an <iframe>
where the content is cut off from the rest of the document; however, when we create a shadow root, we still have total control over that part of our page, but scoped to a context. This is what we call encapsulation.
If you’ve ever written a component that reuses the same id
or relies on either CSS-in-JS tools or CSS naming strategies (like BEM), shadow DOM has the potential to improve your developer experience.
Imagine the following scenario:
<div>
<div id="example">
<!-- Pseudo-code used to designate a shadow root -->
<#shadow-root>
<style>
button {
background: tomato;
color: white;
}
</style>
<button id="button">This will use the CSS background tomato</button>
</#shadow-root>
</div>
<button id="button">Not tomato</button>
</div>
Aside from the pseudo-code of <#shadow-root>
(which is used here to demarcate the shadow boundary which has no HTML element), the HTML is fully valid. To attach a shadow root to the node above, we would run something like:
const shadowRoot = document.getElementById('example').attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `<style>
button {
color: tomato;
}
</style>
<button id="button">This will use the CSS color tomato <slot></slot></button>`;
A shadow root can also include content from its containing document by using the <slot>
element. Using a slot will drop user content from the outer document at a designated spot in your shadow root.
See the Pen
Shadow DOM style encapsulation demo by Caleb Williams (@calebdwilliams)
on CodePen.
HTML templates
The aptly-named HTML <template>
element allows us to stamp out re-usable templates of code inside a normal HTML flow that won’t be immediately rendered, but can be used at a later time.
<template id="book-template">
<li><span class="title"></span> — <span class="author"></span></li>
</template>
<ul id="books"></ul>
The example above wouldn’t render any content until a script has consumed the template, instantiated the code and told the browser what to do with it.
const fragment = document.getElementById('book-template');
const books = [
{ title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' },
{ title: 'A Farewell to Arms', author: 'Ernest Hemingway' },
{ title: 'Catch 22', author: 'Joseph Heller' }
];
books.forEach(book => {
// Create an instance of the template content
const instance = document.importNode(fragment.content, true);
// Add relevant content to the template
instance.querySelector('.title').innerHTML = book.title;
instance.querySelector('.author').innerHTML = book.author;
// Append the instance ot the DOM
document.getElementById('books').appendChild(instance);
});
Notice that this example creates a template (<template id="book-template">
) without any other Web Components technology, illustrating again that the three technologies in the stack can be used independently or collectively.
Ostensibly, the consumer of a service that utilizes the template API could write a template of any shape or structure that could be created at a later time. Another page on a site might use the same service, but structure the template this way:
<template id="book-template">
<li><span class="author"></span>'s classic novel <span class="title"></span></li>
</template>
<ul id="books"></ul>
See the Pen
Template example by Caleb Williams (@calebdwilliams)
on CodePen.
That wraps up our introduction to Web Components
As web development continues to become more and more complicated, it will begin to make sense for developers like us to begin deferring more and more development to the web platform itself which has continued to mature. The Web Components specifications are a set of low-level APIs that will continue to grow and evolve as our needs as developers evolve.
In the next article, we will take a deeper look at the HTML templates part of this. Then, we’ll follow that up with a discussion of custom elements and shadow DOM. Finally, we’ll wrap it all up by looking at higher-level tooling and incorporation with today’s popular libraries and frameworks.
Article Series:
- An Introduction to Web Components (This post)
- Crafting Reusable HTML Templates
- Creating a Custom Element from Scratch
- Encapsulating Style and Structure with Shadow DOM
- Advanced Tooling for Web Components
Hi, awesome post! Great to see some more love for Web Components :)
I noticed that part 5 of this series will be about advanced tooling. We’ve been doing a lot of work around tooling and web components at http://www.open-wc.org
Maybe we can be in touch and help you out with anything? Would love to hear from you!
I already got a headache… but it’s a good start… looking forward to what’s coming next ;)
Great introduction!! Might be worth adding a warning so people don’t XSS themselves (unless I missed it).
For our new huge long-term enterprise project we have chosen web components and vanilla JS instead of frameworks for UI. Project supposed to be supported for decades and we don’t want later to support obsolete frameworks (like jQuery now) or migrate from one framework on the fly. So vanilla and WC as official standard specification would live very long time.
For smaller projects and start-ups, frameworks might be a silver-bullet, because you can create a lot of stuff out-of-box. But in huge enterprise environments is really hard to migrate to other technology to stay up to date, so standards come here to play for large-scale projects WC is a very good solution.
I finally understand what the shadow dom is
Great article!
Good tutorial, although I would advise against using querySelector in examples. It’s very slow compared to the other selectors, as it requires parsing CSS.
Thanks for the feedback. I wouldn’t necessarily say
querySelector
is slow, it’s not just as fast as some alternatives. It can still run up to 7,000 tasks per millisecond, which is pretty crazy when you actually think about it. For most operations, the convenience and versatility ofquerySelector
makes it a great choice although you are absolutely right thatgetElementById
andgetElementsByClassName
are faster thanquerySelector
.This looks to be a much needed article on an overlooked topic in these days of JS Frameworks. I’ll be following the series with great interest.
web component one of the most enjoyable technology come to this industry ‘web’ and is the same technology used by all javascript frameworks this days.
Hi Caleb, great to see a deep dive on web components here! Thanks for taking the time. I’m curious as to why you chose to feature HTML Imports in this article. To the best of my knowledge the specification has been dropped industry wide and will be removed from Chrome shortly. Much of the web components community seems to have settled on ESModules (with the possibility of HTMLModules and CSSModules in the future) as the module/de duplication strategy of choice. Why touch on it herein where it could be misconstrued as actively a part of the family of technologies that power web components now and into the future?
Hey Westbrook, that’s a fair point. I thought that there was enough general knowledge about HTML imports that it was worth mentioning. You’re absolutely right that HTML imports have been deprecated in favor of HTML modules, though. Some of the language here was confusing. I’ll look into getting it updated.
HTML Imports used to be part of the Web Component spec but has been deprecated and was replaced with ES6 module imports. Chrome used to support it from version 36 to 72. But it has been removed.
The section at the top, “HTML Imports is likely to be the …” should be covering HTML Modules. HTML Imports have been shipping in Chrome for some years and are now being deprecated: https://www.chromestatus.com/features/5144752345317376
Hey Eric, thanks for the feedback. I think something got lost in translation during writing/editing. I’ll get that updated.
This is a great article, except you shouldn’t use
appendChild
directly onto the DOM when looping as that will result in layout thrashing (i.e. triggering a ton of repaints). One option is instead to create a document fragment, append your elements to that, then append that fragment to the DOM. I’m looking forward to reading the rest of the series :)I’d remove the reference to HTML Modules. That part of the spec was deprecated and will NOT be used going forward.
Hi Caleb! Nice article :) I believe there are 4 specifications that comprise Web Components, the fourth being ES Modules :) Not sure how much you wanna say on the topic, but might be nice to add!
https://www.webcomponents.org/introduction
We’ve been creating our design system with web components since the start of this year. We have dropped the shadowRoot option due the fact we have multiple skins and did not find a way (yet) to make that requirement work with the shadowRoot. Even though I love the idea (as a designer) of capsulating code. What is your take on maintaining skins with web components and shadowRoot?
Reader Jon Nyman, after the buzzer, so posting on his behalf:
Regarding CSS and web components is a bit of a conundrum. There are two ways to use the pages CSS that I’m aware of. First, you can use the DOM directly instead of the shadow root. Second you could use slots, which use the pages CSS. Then you can namespace your CSS for your custom element, say you have the custom element
<i-like-cheese>
then you could have your CSS look like so: