About Web Components

21 min readMar 13, 2023

Recently I published a blog post with a step-by-step tutorial showing how to build your first Web Component in Vanilla JS. In this post, I want to take a step back and talk more about the “what” of Web Components and less about the “how”. There is so much to say on this subject, so let’s dive in.

A Brief Historical Perspective

Since the dawn of the web, makers have been pushing the platform to do all sorts of things it wasn’t originally intended for. Heck, I remember rolling my own Ajax library in 2004 back before the term “Ajax” had been coined. I was using it to build browser-based form and workflow designers, both of which needed to auto-save changes to the server. Yeah, almost 20 years ago we did stuff like that, and before that too. But it was hard. We had to invent everything, build everything from scratch, and often twist the platform.

Then along came some libraries to help. The first libraries I remember using were Scriptaculous and Prototype. Finally, I could throw out my home grown and maintained ActiveX code and let a library handle the messiness of Ajax for me. Huge win.

Eventually, XMLHttpRequest was standardized, and we technically didn’t need a special library for Ajax anymore. Some of us tossed our libraries and started using the platform directly. But as time went on, we found the raw XMLHttpRequest API to be less than adequate, and we started using micro libraries to fill in the gaps and manage complexity. More time passed, and the platform grew again, introducing the Promise and Fetch APIs. In response, we shed our libraries again and returned to the platform.

Why do I bring up Ajax in an article about Web Components? I want to show you that what I’ve described is a pattern. It happens repeatedly in various forms in all industries. I’d summarize it like this:

  1. Individuals recognize a lack or problem and build a bespoke solution.
  2. The community begins to discuss the problem more broadly as shared solutions emerge. Sometimes these solutions are proprietary and sometimes open source.
  3. As these solutions mature and broad consensus is reached, standardization occurs.
  4. Goto Step 1.

We can see this with components as well. In the early days of HTML, there was no way to create reusable components. In response, many of us created our own libraries for building classes and components. Then the first generation of shared libraries appeared: jQuery, MooTools, YUI, and the like. Then a second generation: AngularJS, Backbone, Knockout, etc. Then a third generation: Angular, React, and Vue. By this time, common patterns were well-established.

While various folks were busy hacking away on this third generation of libraries, others had already started the important work of standardization. In May 2013 Google VP Linus Upson announced Web Components during Google’s annual I/O keynote, a technology which Google had already been working on for several years.

With three generations of non-standard libraries vying for mindshare, it took some time to gain consensus around Google’s Web Component proposals, and even longer to get them implemented across browsers. But in January 2020, with the shipping of the new Chromium-based Edge browser, all browsers had finally shipped the features that had been dubbed “Web Components v1”.

What are Web Components?

If it’s not a framework or a library, what is it? What took 7+ years to gain consensus on and ship?

I like to refer to Web Components as an “umbrella term”. It covers a collection of capabilities that have been added to the HTML, JavaScript, and CSS specifications and implemented in major browsers. Together, they provide the Web Platform with a built-in first-class component system. Let’s go through the key pieces of “Web Components v1” one at a time.

HTML Templates

HTML Templates were a major addition to the HTML language and parser. It’s a declarative way to create HTML that’s intended to be cloned and used repeatedly. The template element, and its treatment by the HTML parser, are unique because its DOM subtree is placed into a DocumentFragment accessible via the content property, and all the DOM in the fragment is inert. This means that it is properly validated by the parser but does not render.

Templates fit into the Web Component story by providing a way for a component to declare its internal rendering structure once, and then use it each time a component is created. This is done by cloning the template and then typically appending it to the shadow root. Here’s a brief bit of code that shows how that might work.

Define a Template in HTML

<template id="my-template">
Hello from template! My content is inert...

Use a Template in JavaScript

// Do this once.
const myTemplate = document.getElementById("my-template");
const myFragment = document.adoptNode(myTemplate.content);

// Do this for each component instance.
const clone = myFragment.cloneNode(true);

NOTE: While templates are important to Web Components, like most other features under the Web Components banner, they are quite useful for a variety of additional use cases as well.

JavaScript Classes

In July 2015, the first update to the JavaScript language in six years was standardized. It might have been the most significant update in the language’s history. There were many new features added at this time. One of the biggest was classes.

While classes in JavaScript may be controversial in the minds of some, there’s no denying that the DOM is a class-oriented hierarchy. So, it makes sense that extensions to the DOM would need to somehow fit into the same model, both conceptually and technically.

The most important parts of classes, as far as Web Components are concerned, are the ability to inherit from HTMLElement, the constructor and member methods (for lifecycle hooks), and static getters (declarative reactivity). I’ll show how each of these features come into play in the next section.

Custom Elements

Custom Elements enable adding new tags to HTML. When most folks think of Web Components, this is likely the spec they are thinking of. Though, as I’ve been describing here, it’s only one part of the larger puzzle.

There are several parts to the Custom Elements spec. The gateway to this feature, is the ability to define a new element. This is done using the customElements global object, which has a define method used to create new tags. As mentioned above, classes are a critical part of how to define an element. You must inherit a class from HTMLElement and pass it to the define API along with your desired, hyphenated tag name. Here’s code for the simplest possible custom element:

Define a Custom Element in JavaScript

class MyElement extends HTMLElement {}

That element doesn’t do anything, but it does meet the minimum requirements.

Typically, you’ll want to do more, such as hook into the lifecycle, respond to some attributes, and render into the shadow dom. For that, you’ll need to leverage additional class features.

Define a Custom Element that Uses Lifecycle Hooks

class MyElement extends HTMLElement {
static get observedAttributes() {
return ["my-attr"]; // tell the platform about reactive attributes

constructor() {
// custom construction logic such as creating your shadow DOM

connectedCallback() {
// this lifecycle hook is called by the platform when your
// element is connected to the DOM

disconnectedCallback() {
// this lifecycle hook is called by the platform when your
// element is disconnected from the DOM

attributeChangedCallback(name, oldValue, newValue) {
// this lifecycle hook is called whenever one of your
// observed attributes changes in value

customElements.define("my-element", MyElement);

NOTE: There has been some confusion over when the constructor runs, relative to the connectedCallback. The constructor always runs before the connectedCallback. The constructor runs either:

1. When the element is created, if the element is already defined.
2. When an existing element is upgraded, if it was used before it was defined.

The connectedCallback runs when the element is connected to any other document-connected element.

If you are interested in exploring custom elements more, please check out my previous post on the subject.

Shadow DOM

So far, we’ve seen specs for creating components based on JavaScript classes along with specs for registering them. We’ve also seen specs for creating templates that can be used for rendering. However, we haven’t yet looked at where you are expected to render your template to. That’s where the Shadow DOM spec comes in.

NOTE: I mentioned this in a previous post but wanted to share this thought again here as well. Many don’t find the terms “Light” and “Shadow” DOM particularly intuitive or explanatory. Instead, it may be useful to think of the “Light” DOM as the “Semantic” DOM. The “Shadow” DOM can then be thought of as the “Render” DOM. Shadow DOM is a private document that describes how the element will render itself, without affecting semantics. Most native component models have similar concepts, e.g., Logical vs. Visual trees.

Shadow DOM allows you to create an encapsulated DOM subtree for your component to render to. Just call attachShadow(options) and specify mode in your options and the browser will set up the DOM tree and hand you back the root.

Attach a Shadow Root in JavaScript

const shadowRoot = myElement.attachShadow({ mode: "open" });

You can specify either open or closed mode. The closed mode makes it a bit harder for your component’s internals to be manipulated by 3rd parties, but it’s hackable. So, don’t rely on this as a strong security boundary. Most folks tend to agree that open mode should be your default unless there’s special justification to do otherwise.

Shadow DOM has some interesting properties. Here’s a summary:

  • DOM Encapsulation — You get your own mini-document to render to, with its own id-space and root node.
  • Style Encapsulation — The styles you put in your Shadow DOM do not leak out and affect the surrounding DOM. Styles external to your shadow root also don’t leak in and affect your element.
  • Slot Composition — You can leverage special <slot> elements in your Shadow DOM that tell the browser how to compose your element with other elements.
  • Event Re-targeting — Events that are emitted from within your Shadow DOM will appear to come from the host element of the shadow root.

Together, these capabilities provide a compelling, platform accelerated solution for several common component needs.

NOTE: Like templates, you can use Shadow DOM outside of the context of a Web Component. Just call attachShadow() on a supported element and away you go.

CSS Custom Properties

What is under the Web Components “umbrella” is a little fuzzy. Some folks include CSS Properties in the list, and some don’t. I like to include them because they are such a fundamental part of practical component implementation.

CSS Custom Properties, also known as CSS Variables, allow you to parameterize your styles with variables that you can set further up in the DOM tree. There’s something particularly special about CSS Custom Properties as they relate to Web Components. They have the ability to pierce the Shadow DOM. As a result, systems of components and applications can fully encapsulate their styles while still sharing a set of variables. Common examples are system-level characteristics like accent and fill color, corner radius, shadow depth, density or spacing multipliers, etc.

NOTE: If you want to explore the full range of Web Component standards, beyond just the basics described here, check out my article “2023 State of Web Components”.

Web Component Advantages

Now that we know how Web Components came about and what they are, let’s look at a few advantages they bring to the table.


This first point is one I’ve made already but it’s worth being explicit about. The collection of capabilities under the Web Components umbrella is part of the HTML, JavaScript, and CSS standards. These standards are implemented in every browser today and have been for over three years, as of the writing of this article. Thus, they provide a component technology that has a wider reach than any other. Web Components are quite literally everywhere. Not only are they universally supported, but they are widely used. You might find it interesting that over 18% of all pages loaded by Chrome use Web Components.


Another benefit that is implicit in the design of Web Components is their inherently interoperable nature. Because every Web Component inherits from HTMLElement, every Web Component naturally works with any library that understands the DOM. Yes, even going back to Scriptaculous and reaching forward to frameworks like Svelte and Solid.

Incremental Adoption

The strength of interoperability unlocks the benefit of incremental adoption. Unlike most JavaScript frameworks, you do not have to throw out or re-write your existing code. You can start to incrementally adopt Web Components within your existing codebase. You can start with just one component and then evolve your codebase from there. Again, let me emphasize there is no need to re-write existing apps in order to take advantage of Web Components.

Decoupling Components from Frameworks

Once you realize that you can use Web Components within existing frameworks, this presents another advantage. You no longer have to couple your technical decisions around reusable components to your technical decisions around application frameworks. You can now use Web Components for maximum interoperability while picking the best application framework depending on the nature of your app.


Web Components have clear performance benefits. There are several reasons for this:

  • The core capabilities of Web Components are implemented in C++/Rust as part of each browser’s native HTML parser and runtime. JavaScript frameworks simply can’t match the speed and memory efficiency of C++.
  • Because the component model is implemented by the browser, less JavaScript needs to be downloaded, parsed, and executed before a component is able to render.
  • With JavaScript frameworks, the browser doesn’t “see” the components and thus can’t optimize for them. It just looks like one gigantic DOM tree to the runtime. With Web Components, the browser knows about every component and its boundaries, and can perform various internal optimizations to improve performance. This is particularly important in applications with large amounts of CSS where the absence of Web Component Shadow DOM makes it impossible to fix certain rendering performance problems. Non-web component libraries literally block the browser from optimizations it could otherwise perform.
  • The Web Component model favors modern reactivity patterns and surgical DOM updates that massively outperform the Virtual DOM approaches of libraries like React. You can see this in the way that observedAttributes and the attributeChangedCallback work.

There are a few additional things I’d like to say about performance. It’s not enough to speak about what makes Web Components themselves fast. It’s important to understand the implications:

  • Economics — Not everyone can afford the newest iPhone or an expensive desktop computer, nor do they have the highest bandwidth internet connection. So, picking a high-performance, small solution is important when evaluating web tech. Large or slow frameworks can make software completely unusable on lower end or older hardware. Software developers and designers are not typically developing on normal hardware and often don’t realize this problem. We need to be very careful about how our technology choices affect people with different socio-economic statuses. Furthermore, with the current economic situation being what it is, we should expect people to slow down their purchases of new mobile devices and desktop/laptop computers, staying with older hardware longer.
  • Environment — While web services tend to run on a relatively small number of VMs under our control, this is not the case with UI. UI code runs directly on the user’s machines, using their hardware, their internet connection, and their power. Whereas a set of microservices may run on 80–100 VMs (or even a few thousand), your UI is likely to run on 80–100 thousand computers, maybe millions. In light of this, we must select technologies that are environmentally responsible and energy efficient. This means we must care about performance and payload size. Native browser implementations in C++ help here.
  • Experience — Compelling digital experiences matter today. There’s no going back to the web of the early 2000s. But there’s a cost to everything we do. Gradients, transparencies, shadows, animations, etc. They all chip away at the available processing capabilities of a computer. This is especially problematic on lower-end devices. To address this problem, we need our UI libraries and frameworks to be as small and fast as possible, with the highest potential for platform optimizations. Web Components, due to their platform native nature and superior performance, give more “headroom” for design to work within when creating the best possible experience for people.


Web Components provide native DOM-level isolation for HTML and CSS. This ensures that components built by one team cannot affect the rendering of components built by another team. All CSS is fully encapsulated and protected by the Web Platform itself. This is a critical enabler for larger, plugin-based systems, and especially for open ecosystems.


Web Components separate their interface from their implementation. It is the platform that provides the interface for the components via the DOM while the author provides the implementation. This is different from a JavaScript framework like React where the interface and the implementation are one and the same. This critical difference makes it possible to entirely swap out the implementation of any Web Component without having to rebuild or redeploy dependent UI or libraries. This ability to version components and apps independently, then reconnect them at runtime through the browser’s dynamic linking, is critical for modular, extensible ecosystems where lock-step shipping of all systems is not logistically feasible.


Another benefit of Web Component’s separation of interface and implementation is the ease with which you can enable UI/UX experimentation. With Web Components, trying a new version of a component only requires making an alternate customElements.define() call. No changes to any dependent systems are required. I’ve seen folks struggle to grasp this idea in the past, mostly likely due to the fact that with frameworks like React, the component implementation is statically linked at the call site. That’s just not true of Web Components. Because of the unfamiliarity with this idea, I think it’s worth showing some code.

Imagine you have a profile UI, and within that UI is an avatar Web Component that renders a picture of the user above their name. After implementing the <ui-avatar> element you would then have some code like this to define it with the platform.

customElements.define("ui-avatar", UIAvatar);

And your profile UI would use it like this.

<section class="profile">
Rob Eisenberg
<img src="some/avatar.png" slot="media">

Now suppose you want to try out some different ideas for the avatar component. Maybe you want to try a different visual design, for example. You’d like to try this out on a subset of your users to see what they think. As long as you keep the name, attributes, and slots the same (the DOM interface), you don’t have to change any of your profile code or even ship a new update to that component. You just need to switch out the one line of code above with a feature flag check:

if (client.variation('experiment-avatar', false)) {
customElements.define("ui-avatar", ExperimentalUIAvatar);
} else {
customElements.define("ui-avatar", UIAvatar);

NOTE: A more advanced implementation could dynamically import the alternate version or even be distributed through a different bundle. That’s outside the scope of this article though.

Future Compatibility

There are several new capabilities that the W3C has planned for HTML which are only usable from within a Web Component (more on this in the future). By leveraging Web Components today, you position yourself to best take advantage of future innovations in the Web Platform. Whether that’s an entirely new feature or new optimizations, you’ll be able to use them immediately.

JavaScript Not Required

While JavaScript is required to create Web Components (more on that subject in the future too), individuals who want to use an existing Web Component do not need to know or write any JavaScript code to do so. Web Components open the creative possibilities for designers, product managers, copy writers, and more to experiment with real components without needing to code, involve engineers, or deal with build processes. If we want to create the most inclusive maker culture possible on the web, this is a critically important detail.

Debunking Myths and Misconceptions

UPDATE: For an expanded and evergreen version of this section, see the dedicated post “Debunking Web Component Myths and Misconceptions”.

Unfortunately, there’s a lot of bad information on Web Components in the wild. Some of it is just old and out of date content. Some of it was incorrect from the beginning. Sadly, some of it is complete FUD or utter nonsense. Let’s take a few minutes to make sure we’ve got our facts straight.

Web Components are supported in every browser.

Some folks still seem to get confused about browser support. To be clear, Web Components v1 have been supported in every browser for over three years as of the writing of this article. Here are two things that I think may be behind the confusion:

  • Safari specifically doesn’t implement what are called “customized built-in elements”. This is the ability to inherit from base classes other than HTMLElement, such as HTMLParagraphElement. W3C members from Safari’s engineering team rejected that part of the specification based on OCP violation concerns (which I agree with) and UA challenges. Safari adamantly wants this removed from the spec since it will never be implemented. While it may be handy to inherit from some built-ins, doing so means it would be impossible to safely evolve the built-ins and standards, adding or fixing features after that point in time. In practice, there is very little need for this and there are alternative ways to get a similar end result. This should not be treated as part of the spec and will likely be dropped in the future.
  • Searching for “Web Components” on caniuse.com yields results that are dubious at best. Here are a few things that are incorrect or confusing:
    – The results show that Safari doesn’t fully implement Web Components in any of its versions. That is incorrect! See the previous bullet point for the explanation. In short: Safari doesn’t support an unnecessary, confusing, problematic proposal that will likely be removed from the spec. You don’t need it to build Web Components. Safari supports all the necessities.
    – The results show that no browser supports “HTML Imports”. You may note that I haven’t mentioned this “feature” of Web Components at all. That’s because this was part of the v0 Web Components proposal and was removed years ago. In other words, it’s not part of Web Components at all.
    – Declarative Shadow DOM is only partially supported. Again, I haven’t talked about this yet because it’s not part of v1 Web Components. This is a newer proposal that can be thought of as part of the 2nd or even 3rd iteration of Web Components. So, it’s still being implemented by browsers. I’ll be talking about this spec more in an upcoming post. In any case, you don’t need this to create Web Components.

Web Components can be SEO friendly.

Like HTML in general, Web Components can be assembled in a way that yields great SEO results or they can be assembled in a way that doesn’t. It’s up to you. Like with all HTML, you have to think about SEO and architect for that characteristic. The main technique to keep in mind where Web Components are concerned is that you want your SEO critical content to be in the Light DOM. Do not put that into the Shadow DOM. Follow this rule of thumb and your SEO will be fine.

Web Components can be accessible.

Similar to SEO, you have to architect for accessibility. Web Components aside, if you don’t write your HTML in an accessible way, then it may not be accessible. The same goes for Web Components. Think about and architect for accessibility from the beginning.

Having said that, I want to acknowledge that accessibility is difficult and takes a lot of time. This is one of the reasons why FAST exists. One of things it provides is a library called @microsoft/fast-foundation that specifically aids in the development of accessible versions of the most foundational Web Components (e.g. button, menu, tree-view, select, combobox, etc.)

You can share styles between Web Components.

A common concern I hear from folks is that they can’t share existing styles with Web Components due to Shadow DOM encapsulation; or that they can’t share any form of common styles. However, this is incorrect. The standards support this, and most Web Component libraries make this very easy as well. All you need to do is create a style sheet and add it to the adoptedStyleSheets collection of the Web Component. The sheet can be shared across any Web Component that needs its styles. With CSS Script Modules, this is incredibly easy.

import sheet from './styles.css' assert { type: 'css' };

shadowRoot.adoptedStyleSheets = [sheet];

You can pass any data type to a Web Component.

Many people forget that HTML elements have both an attribute and a property interface. While it's true that a Web Component can only receive string data through its attributes, that’s not true of its properties. Like any JavaScript object, you can set a Web Component’s properties to any type of data.

myAvatar.person = new Person(); // It's just JavaScript after all.

In addition to this, popular Web Component libraries make it easy to bind any type of data to a component via their template system.

You can create Web Components without classes.

Classes are basically just special syntax over functions. If you really don’t want to use class syntax for Web Components, you can do this:

function MyElement() {
return Reflect.construct(HTMLElement, [], MyElement);

Object.setPrototypeOf(MyElement.prototype, HTMLElement.prototype);
Object.setPrototypeOf(MyElement, HTMLElement);

customElements.define('my-element', MyElement);

Personally, I prefer this though:

class MyElement extends HTMLElement {}
customElements.define('my-element', MyElement);

You can use Web Components in React.

Contrary to what seems to be the popular thought, you can use Web Components in React. Here are the basic React docs on this.

I think the confusion stems from the fact that React’s message has been that it doesn’t “officially” support Web Components. From what I can tell, the team is planning to “fix” this in React 19 and has already addressed it in the experimental releases.

You might be wondering why the React team is hesitant to say they officially support Web Components. To be blunt, React has always treated the DOM in an antagonistic way, quite different from the friendlier approaches that almost every other modern JavaScript framework uses. React’s system doesn’t recognize the difference between an HTML attribute, a property, and a boolean attribute. These are all core HTML implementation details that have been part of the basic programming model for 20+ years. But because React doesn’t distinguish between these cases, it has a hard time natively interoperating with Web Components that have properties and boolean attributes in addition to standard attributes. Additionally, React doesn’t properly support the DOM’s event system. It creates a synthetic event system. This causes issues not just for Web Components, but also for many non-React libraries. The changes being made (hopefully) for React 19 will address these issues.

However, you don’t need to wait for the React team. There are several ways to use Web Components in React today without having to deal with the above-mentioned issues.

  1. You could use something like @skatejs/val to handle the interop between Web Components and React’s virtual DOM.
  2. You could use something like FAST’s React Wrapper library, that automatically turns any Web Component into a React component.
  3. You can always use the Web Component directly, knowing that you can just handle properties and events manually using refs.

All three of these are viable options that I have seen used in large-scale React/Web Component hybrid systems. Bottom line: yes, you can use Web Components in React.

NOTE: In some cases, you may run into trouble with these techniques in an existing React application. If so, make sure your transpiler is configured correctly. create-react-app has an unfortunate default configuration that is not friendly to modern Web Components. You will want to eject and properly configure things. You can read more about that here.

Web Components have a fast startup time.

Somewhere I read a blog post that indicated Web Components might not have a fast startup time. The author had no basis for their statement but was merely conjecturing. In my experience working with dozens of teams at Microsoft from 2020 through the end of 2022, and many teams outside of Microsoft, Web Components have great startup time (see the performance section above for some explanation on why). Back when I advised and trained the MSN team on Web Components, we found that by moving MSN from React to FAST Web Components, MSN was able to improve startup time by 30 — 50%. Here’s a blog post written by someone who was on the team at the time when the conversion was being investigated.

Shadow DOM is efficient.

Some folks have raised a concern that Shadow DOM might not be efficient or that it might be too heavy for use at scale. In the last 8 years that I’ve been putting Shadow DOM-based solutions into production, I can tell you that this has not been a problem. Surprisingly, some performance tests we did at Microsoft showed that in some cases, Declarative Shadow DOM could be even faster than Light DOM. Other tests we performed on standard Shadow DOM scenarios showed that the use of Shadow DOM was critical for performance optimization of the browser in codebases with lots of components and large quantities of CSS. It turns out, if your app needs to scale, then you need Shadow DOM to make the browser perform. As mentioned earlier, this is because Shadow DOM gives the browser more information about component boundaries, enabling it to optimize better.

You can server-side render Web Components.

Declarative Shadow DOM is a new standard that enables you to leverage Shadow DOM directly in HTML without using JavaScript, enabling it to be generated on the server. It is supported in Chromium browsers as well as the Safari Technology Preview. Declarative Shadow DOM can easily be polyfilled with only a few lines of JavaScript code. Both FAST and Lit now offer SSR renders for their Web Component libraries. Furthermore, the SSR renderers of both Web Component libraries are built on a shared protocol, enabling them to interoperate and support HTML streaming out of the box.

Large, business-critical software has been built with Web Components.

Adobe built Photoshop with Web Components. Microsoft re-built MSN with Web Components. Google built YouTube with Web Components. Those all seem like pretty business-critical to me.

But are any other well-known companies building with Web Components?

Web Components scale to any size project.

See the previous explanation as well as the performance notes on Shadow DOM. From single components to design systems, to content-oriented experiences and entire applications, Web Components can handle the situation. In fact, you need them to scale beyond a certain point if you want your application to perform.

Wrapping Up

I’m so glad you made it this far! Believe it or not, I cut this post short 😆 I was planning a few more sections on the present state and future of Web Components, but that will have to wait until next time. For now, I hope you found the information in this post valuable and that it will be something you can share with others who are exploring Web Components.

Until next time!

If you enjoyed this look into Web Components, you might want to check out my Web Component Engineering course. I’d also love it if you would subscribe to this blog, subscribe to my YouTube channel, or follow me on twitter. Your support greatly helps me continue writing and bringing this kind of content to the broader community. Thank you!




W3C, WICG & TC39 contributor focused on Web Standards, UI Architecture & Engineering Culture. Former Technical Fellow, Principal Architect & VP-level tech lead.