VOOZH about

URL: https://css-tricks.com/an-introduction-to-web-components/

⇱ An Introduction to Web Components | CSS-Tricks


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.


What are Web Components, anyway?

Web Components consist of three separate technologies that are used together:

  1. 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.
  2. Shadow DOM. Capable of isolating CSS and JavaScript, almost like an <iframe>. This is defined in the Living Standard DOM specification.
  3. 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> &mdash; <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.

Comments

  1. Passle
    Permalink to comment#

    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!

  2. Riad
    Permalink to comment#

    I already got a headache… but it’s a good start… looking forward to what’s coming next ;)

  3. Jeff
    Permalink to comment#

    Great introduction!! Might be worth adding a warning so people don’t XSS themselves (unless I missed it).

  4. Dmitri
    Permalink to comment#

    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.

  5. mr p g waldock
    Permalink to comment#

    I finally understand what the shadow dom is

    Great article!

  6. Danny
    Permalink to comment#

    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.

    • Caleb Williams
      Permalink to comment#

      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 of querySelector makes it a great choice although you are absolutely right that getElementById and getElementsByClassName are faster than querySelector.

  7. Daniel
    Permalink to comment#

    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.

  8. Amir
    Permalink to comment#

    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.

  9. Westbrook Johnson
    Permalink to comment#

    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?

    • Caleb Williams
      Permalink to comment#

      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.

  10. Mike Collins
    Permalink to comment#

    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.

  11. Eric Bidelman
    Permalink to comment#

    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

    • Caleb Williams
      Permalink to comment#

      Hey Eric, thanks for the feedback. I think something got lost in translation during writing/editing. I’ll get that updated.

  12. Benny
    Permalink to comment#

    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 :)

  13. Jesse
    Permalink to comment#

    I’d remove the reference to HTML Modules. That part of the spec was deprecated and will NOT be used going forward.

  14. Emma Wedekind

    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

  15. Rick Leijten

    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?

  16. Chris Coyier
    Permalink to comment#

    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:

    ;(function (){
    const styles = `
    i-like-cheese p { color: orange; }
    `
    const $styles = document.createElement("style")
    document.head.append($styles)
    class ILikeCheese extends HTMLElement {}
    ...
    })();
    
This comment thread is closed. If you have important information to share, please contact us.