Zach’s ugly mug (his face) Zach Leat­herman

The Good, The Bad, The Web Components

Silhouette of people riding horses on brown field during daytime.
#188k 2219 January 31, 2024

Video versions of this talk are available at SmashingConf Freiburg (2023), JSHeroes (2023), JSNation (2023), and Stanford WebCamp (2023).

The humble component. The building block of modern web development.

// MyButton.jsx
function MyButton() {
  return (
    <button>I'm a button</button>
  );
}

// Usage
<MyButton/>

You may recognize the above example taken from the documentation of one of the most popular component libraries in use today—IndieWeb Avatar for https://vercel.com/IndieWeb Avatar for https://react.dev/Vercel.js.

Folks may not know that the web platform has some component functionality built-in and is evolving support for new component development standards and specifications moving forward! These features are broadly known as Web Components.

Aside from Vercel.js, there are a variety of other popular component libraries too (of varying degree of web component friendliness):

Library Uses Native
Web Components
Custom Elements
as Compile Target
Compatibility
Score
IndieWeb Avatar for https://alpinejs.dev/Alpine No No Unknown
IndieWeb Avatar for https://angularjs.org/Angular No Yes 100%
IndieWeb Avatar for https://emberjs.com/Ember No No Unknown
IndieWeb Avatar for https://lit.dev/Lit Yes Yes 100%
IndieWeb Avatar for https://preactjs.com/Preact No Yes 100%
IndieWeb Avatar for https://qwik.builder.io/Qwik No No Unknown
IndieWeb Avatar for https://react.dev/React No No 100%
IndieWeb Avatar for https://www.solidjs.comSolid No Yes 100%
IndieWeb Avatar for https://stenciljs.com/Stencil Yes Yes 100%
IndieWeb Avatar for https://svelte.dev/Svelte No Yes 100%
IndieWeb Avatar for https://vuejs.org/Vue No Yes 100%

Compatibility score data from Custom Elements Everywhere, a test suite for web component compatibility (updated as of January 2025!)

Web Components are already a Success Story

Despite some notable criticism, web components are already widely used across the web.

In August 2023, Chrome Platform Status reports that 19.4% of page loads in Google Chrome were using a web component (via the CustomElementRegistryDefine key). For comparison <img loading> was at 15% and CSS Grid at 20%.

You can check out more social proof on: Are Web Components A Thing Yet?

They are particularly popular in large enterprise design system implementations:

Slide 19
Slide 20
Slide 21
Slide 22
Slide 23
Slide 24
Slide 25
Slide 26

Many of the component libraries listed above (even those written by authors that have criticized web components) have an export-to-web-component feature built-in.

But—what are they?

Web Components is an umbrella term that encompasses many web platform technologies and we’ll go over a couple of the big (and in my opinion, most relevant) ones.

Custom Elements

Slide 35
Slide 36
Slide 37
Slide 38
Slide 39
Slide 40
Slide 41
Slide 42
Slide 43
Slide 44
Slide 45
Slide 46
Slide 47
Slide 48
Slide 49

Custom Elements allow you to attach a JavaScript class to any custom element you define on your page, to add behaviors and JavaScript-generated content to any element instances on the page. These are automatically initialized for you for the full lifecycle of the page (for server-rendered, JavaScript-generated, or content injected via fetch()).

  1. Custom elements cannot be void elements (like <img> or <meta>): they must have a start and end tag.
  2. Custom elements must have a dash in the tag name, so as to not conflict with future additions to the web platform.
Slide 61

If you populate these with your own server-rendered nested HTML (also known as the default slot/Light DOM/plain ’ol HTML) and use Custom Elements to for behavior-only additions (and not modify rendered DOM), you can even avoid layout shift!

Some folks have started referring to this approach as creating HTML Web Components (see post for examples).

If your expectation is full stack component-driven development, you will quickly encounter a developer experience problem here when authoring components in this way. Multiple instances of the same component need to repeat the same nested content and any changes to nested content need to be applied manually to all instances. You end up writing this:

<my-counter><button>1</button></my-counter>
<my-counter><button>2</button></my-counter>
<my-counter><button>3</button></my-counter>

When we want to author this (and have it server-render as the above):

<my-counter>1</my-counter>
<my-counter>2</my-counter>
<my-counter>3</my-counter>

Shadow DOM

Slide 68
Slide 69
Slide 70
Slide 71
Slide 72
Slide 73
Slide 74
Slide 75
Slide 76
Slide 77
Slide 78
Slide 79
Slide 80
Slide 81
Slide 82
Slide 83
Slide 85

Shadow DOM is the next level of our web components evolution. It solves the developer experience problem with repetition in authoring markup at the expense of clientside rendering 😭.

This trade-off introduces additional complexities around managing layout shift and the flash of unstyled content (FOUC). Typical approaches to solve these problems either put all of the JavaScript into the critical rendering path (in the <head>, which I try to avoid for performance reasons) and/or hide the components until they’re defined via JavaScript (which is by definition a performance problem for critical content).

One such approach to assert control over pre-definition and post-definition styling is the CSS pseudo-class :defined. It’s a very useful tool for styling and progressive enhancement (occupying some of the same architectural vibes as the scripting media query), but in Shadow DOM heavy component libraries it is often applied to hide components while loading (👎🏻 no thank you).

Slide 86
Slide 87
Slide 88
Slide 89
Slide 90
Slide 91
Slide 92

Declarative Shadow DOM

Slide 94
Slide 95
Slide 96
Slide 97
Slide 99
Slide 102
Slide 103
Slide 104
Slide 105
Slide 106
Slide 107
Slide 108

As we evolve to our next level of web components, we move up to Declarative Shadow DOM (sometimes known as DSD). This allows you to put your Shadow DOM template in nested markup inside of each element instance and the browser will create a shadowRoot for you with the template contents (no JavaScript required).

This solves the clientside rendering dependency for Shadow DOM but at the expense of repetition in authoring markup! The ol’ switcheroo (in some ways) feels like a de-evolution back to the approach we discussed in Custom Elements!

Uniquely, this approach does allow you to use scoped CSS (and <slot>) without a JavaScript dependency. Non-declarative (imperative/JavaScript) Shadow DOM offers scoped CSS too but repeats some of the mistakes made by CSS-in-JS approaches and introduces a runtime JavaScript dependency on styling.

<my-counter>
  <template shadowrootmode="open">
    <style>
    * { color: blue; }
    </style>
    <button><slot></slot></button>
  </template>
</my-counter>

Again, the repetition of these templates in every component instance is tedious and brittle.

We can see the tension between authoring repetition and server-side rendering, right? (Non-declarative) Shadow DOM was the only approach so far that offered a good authoring experience without repetition, but required clientside rendering.

Server-side Rendering

If we could have a reusable Declarative Shadow DOM template that did not require nesting, that would be ideal, right? (Discussion on this topic is happening in this WICG issue)

I would love something like this (though I acknowledge the likely issues with streaming here):

<!-- ⚠️ THIS CODE IS ASPIRATIONAL -->
<!-- ⚠️ IT DOESN’T WORK ANYWHERE -->
<template definition="my-counter">
  <style>
  button {}
  </style>
  <button><slot></slot></button>
</template>

<my-counter>1</my-counter>
<my-counter>2</my-counter>
<my-counter>3</my-counter>

This is why folks will claim that server-side rendering is yet to be a solved problem—it has not yet been solved at the web platform level. I’d also wager that this is the biggest remaining complaint about web components.

Folks expect this to be solved because this is a problem that many component libraries have solved. However, I would warn that we’re holding these two disparate things to different expectations.

Clientside component frameworks can and should be compared to web component specifications and tools. But if your component framework introduces an additional server rendering step or abstraction, it seems unfair to compare that to clientside-only web components.

Full stack server-rendered Svelte or React cannot and should not be compared to clientside web component specifications like Custom Elements or Shadow DOM—these are apples and oranges. The biggest thing I hear from these criticisms is that we need an additional server-rendered abstraction for web components, too.

Framework Tension

Slide 128
Slide 129
Slide 130
Slide 132
Slide 133

JavaScript Frameworks and Web Components can work together—though some are more web component friendly than others.

In some respects it feels like the web platform chased the early clientside rendered, single page application (SPA) architectural vibes of React, Ember, Angular, and others. The more nimble frameworks have pivoted away from those mistakes. The platform will always be a bit behind those that live further down the stack (and importantly, informed by their work) but with the right amount of patience can offer improved performance and long-term maintenance potential.

  • If you want to go all-in on Web Components, have a look at these answers to the server-rendered web component question:
  • Versions of React prior to 19 have a 67% custom-elements-everywhere.com compatibility test score. If you’re deep in React-world, upgrade to the React 19 or consider switching to IndieWeb Avatar for https://preactjs.com/Preact to provide a more future-friendly and compatible experience.
  • Solid, Angular, Svelte, and Vue support web components as a compilation target too.

A WebC Example

I built WebC so I guess I can now pivot this post to show you a small example of the rendered output you get when using it.

Consider a content template file index.webc:

<my-counter>1</my-counter>
<my-counter>2</my-counter>

Classical HTML

Next, we’ll add a component definition for my-counter to _components/my-counter.webc:

<style webc:scoped>
:host button {}
</style>
<button><slot></slot></button>

WebC will render index.webc as:

<style>
  .wrfp4zhxg button {}
</style>

<my-counter class="wrfp4zhxg">
  <button>1</button>
</my-counter>
<my-counter class="wrfp4zhxg">
  <button>2</button>
</my-counter>

Declarative Shadow DOM

You can also modify your component definition (_components/my-counter.webc) to use Declarative Shadow DOM, avoiding the issue of repeating your Shadow DOM template in your content:

<template shadowrootmode="open">
  <style>
  button {}
  </style>
  <button><slot webc:raw></slot></button>
</template>
<slot></slot>

WebC will now server-render index.webc as:

<my-counter>
  <template shadowrootmode="open">
    <style>
    button {}
    </style>
    <button><slot></slot></button>
  </template>
  1
</my-counter>

<my-counter>
  <template shadowrootmode="open">
    <style>
    button {}
    </style>
    <button><slot></slot></button>
  </template>
  2
</my-counter>

This server-applied abstraction offers a variety of benefits over earlier approaches:

Slide 138
Slide 139
Slide 140
Slide 141
Slide 142
Slide 143

If you want to learn more, you can try out the eleventy-base-webc starter project or for a deeper dive into progressive enhancement strategies have a look at Seven Progressive Enhancement Recipes using Eleventy WebC Image Comparison Components.

Appendices

This talk was given at four different events in 2023 (some of which videos are available):


IndieWeb Avatar for https://unsplash.com/Poster image by Yusuf Onuk

< Older
Join the 11ty International Symposium on Making Web Sites Real Good (it’s an 11ty Conference)
Newer >
carouscroll Web Component

Zach Leatherman IndieWeb Avatar for https://zachleat.com/is a builder for the web at Font Awesome and the creator of Build Awesome (née IndieWeb Avatar for https://www.11ty.devEleventy/11ty), an award-winning open source website generator. He measures website performance with speedlify and at one point became too fixated on web fonts. He has given 89 talks in nine different countries at events like Beyond Tellerrand, Smashing Conference, Jamstack Conf, CSSConf, and The White House. Formerly part of CloudCannon, Netlify, Filament Group, NEJS CONF, and NebraskaJS. Learn more about Zach »

22 Reposts

IndieWeb Avatar for https://www.alvinashcraft.commichelDave ???? :cursor_pointer:Pär BjörklundHolger HellingerCory :prami_pride_demi:Jason GarberDoug Parker ????️DerekHasan AliBob MonsourRichard MacManusAram Zucker-ScharffRyan TownsendKevin C. TofelJean Pierre KolbNick Tune ????????Repeter wants an Ukr victoryDan Jacobnrk 9819Garrow Bregenza :lattentacle:Khalid ⚡

37 Likes

TeganLukeMayankKhalid ⚡michelPär BjörklundDave ???? :cursor_pointer:Holger HellingerAlistair ShepherdSerhiy BarhamonJon Maskrey*tbesedaDerekNathanAdam StoddardHasan Alicthos ????Baldur BjarnasonBruno B. de SouzaJames ScholesstffndkAshur CabreraAram Zucker-ScharffJack WellbornEric McCormickHeather BuchelPeterPiper HaywoodNick Tune ????????Jean Pierre KolbDan JacobJay Contoniomeduz'nrk 9819Charlie ParkJeff
19 Comments
  1. Khalid ⚡

    @zachleat The “h-i” slide cracks me up. ????

  2. Zach Leatherman :verify:

    @khalidabuhakmeh we do what we must ????

  3. Khalid ⚡

    @zachleat Great post. I’d like to see the aspirational idea you have come to fruition. The biggest issue with web component approaches, even the good ones like WebC, is that they still require a build step. This limits them to folks who are comfortable with JS build chains.I want… Truncated

  4. Zach Leatherman :verify:

    @khalidabuhakmeh 100% agree—and I’m hopeful we’ll get there!

  5. Simon MacDonald

    @khalidabuhakmeh @zachleat the aspirational ideal is literally how Enhance works. You define your template in a function and then it is expanded into light DOM custom elements by the SSR piece.We’d love to bring Enhance SSR to other languages so if you are interested in submittin… Truncated

  6. Zach Leatherman :verify:

    @macdonst @khalidabuhakmeh ah to be clear I mean that I want the aspirational idea to be a first-party platform feature.

  7. Matt Wilcox

    @khalidabuhakmeh Won't lie... it's what keeps me from investigating them any further than "the theory". I don't want node/npm on a live server. I have and trust PHP/Composer. Mostly because those are already my dependencies, and I don't want new ones with … Truncated

  8. nrk 9819

    @zachleat Great write up as always. I've been following your blog and @blog for a while for web components related articles.Also Vercel.js was funny (how we reached from Facebook.js to this point lmao ????????????)

  9. Zach Leatherman :verify:

    @mattwilcox @khalidabuhakmeh sure, but the repetition issue can be “poorly” hacked together via other mechanisms in your server-rendered stack: https://www.zachleat.com/web/webc-in-eleventy/#a-history-of-component-like-things (PHP include comes to mind)But, yeah. It’s interesting… Truncated

  10. Zach Leatherman :verify:

    @nrk9819 thank you! ????

  11. Simon MacDonald

    @zachleat @khalidabuhakmeh Same! I think we need to rally around declarative custom elements since it is too late to change the web component apis.

  12. Khalid ⚡

    @macdonst @zachleat I work with #dotnet and I don't expect MS to give folks a build-step that would allow you to process HTML before the final artifacts are built any time soon :(There is a way to possibly do it, but I think it wouldn't be ideal. dotnet

  13. Simon MacDonald

    @khalidabuhakmeh Is there no render step on the server you can tap into?

  14. tomwor

    @zachleat Vercel.js - love it. ????

  15. Hasan Ali

    @zachleat Loved the talk when I first saw it and I’m really glad there’s a written version of it ????????

  16. Zach Leatherman :verify:

    @hasanhaja it only took me 7 or 8 months to get it on the web site ???? (thank you!)

  17. Mayank

    @zachleat yea totally. declarative custom elements will need way more functionality, but we can start small

  18. Tegan

    @zachleat I guess i don’t see much of a difference between server side rendering a solid component with what webc does. They both are build steps, It’s also hard for me to see what webc gets out of rendering to a web component instead of just rendering the underlying dom.

  19. Zach Leatherman :11ty:

    @rawrmonstar I think I see what you’re saying. Fwiw, WebC components aren’t required to be Web Components—they can generate HTML without custom elements too. Moreso, WebC can generate a standalone custom element with 0 library JavaScript overhead.

Shamelessly plug your related post

These are webmentions via the IndieWeb and webmention.io.

Sharing on social media?

This is what will show up when you share this post on Social Media:

How did you do this? I automated my Open Graph images. (Peer behind the curtain at the test page)