Hacker News new | past | comments | ask | show | jobs | submit login
If Web Components are so great, why am I not using them? (daverupert.com)
195 points by emegeve83 on Aug 2, 2023 | hide | past | favorite | 181 comments



The main reason is that they're too low-level to use directly.

They do a lot, but stop just short of being useful without something of a framework on top. I tried hard to use them directly, but found that it was untenable without sensible template interpolation, and without helpers for event binding.

Here's my shot at the smallest possible "framework" atop Web Components that make them workable (and even enjoyable) as an application developer:

https://github.com/dchester/yhtml

It's just ~10 SLOC, admittedly dense, but which make a world of difference in terms of usability. With that in place, now you can write markup in a style not too dissimilar from React or Vue, like...

    <button @click="increment">${this.count}</button>
Whereas without, you need to bind your own events and manage your own template interpolation with sanitizing, handling conditionals, loops, and all the rest.

If we could get something in this direction adopted into the spec, it would open up a lot of use cases for Web Components that it just can't address as-is.


Template Instantiation is suppose to be the answer at least in part, but it’s been held up since 2017[0]

The bigger problem is that the web platform stakeholders (IE the committees that industry influence and the browser makers) simply didn’t listen at all to what regular developers have been saying about what they want from the web platform w/r/t better built in platform provided primitives. It’s seems to me like web components have largely not taken lessons of component based development as everyone has come to expect. It’s still a weird imperative API with no rendering or data binding primitives

[0]: https://github.com/WICG/webcomponents/blob/gh-pages/proposal...


You don't need reactive data binding. You can simply watch for HTMLElement attribute changes and invoke a render method whenever that occurs. It helps to improve your app architecture if you do this. Reactive rendering can be a foot gun and often leads to unnecessary double or triple rendering. It's better to be able to control the rendering explicitly from a single binding which is exactly what HTMLElement offers; you just need to adhere to good architectural principles.

Many modern devs are too deeply involved in the React cult to see past its many over-engineered and flawed abstractions and struggle to see the simpler approach which necessitates adhering to some basic but highly beneficial architectural constraints.


See that's weird, because the parent comments are about using in HTML template binding, and you seem to have suggested instead that a React-like render function model would be better, while simultaneously bashing React.

I am no fan of React, but this comment confuses me.


I don't have a problem with the idea of making each component have a render function which can be called whenever you need to render or re-render the component and I concede that there is some elegance in being able to construct a complex HTML component using a single template string. But you don't need React to do this. This idea of rendering a component whenever its internal state changed existed long before React.

React didn't even invent the idea of reactive components; that was Angular 1. I honestly don't know why React became so popular initially. After Angular 1, there was Google's Polymer which was far more elegant and closer to native Web Components (and fixed many of Angular 1's flaws) - I suspect it's because some devs didn't like that you had to use some polyfills for certain non-Chrome browsers.

Anyway now we have Web Components which work consistently without polyfills on all browsers so I don't see any reason not to use plain Web Components or use something more lightweight such as Lit elements.


And yet the adoption shows that plenty of people see reasons not to use plain Web Components.


Just like how plenty of people see reasons to start wars and waste tax payer money on bureaucracy. Or why billions of people choose to join one religion instead of another... All logical right?

How do you explain different religions being very popular and yet contradicting each other on many critical points? They can't both be right if they contradict each other yet they may both be hugely popular...


> You can simply watch for HTMLElement attribute changes

You know what's really, really, really slow? Anything involving the DOM, reading, writing, whatever.

> often leads to unnecessary double or triple rendering

"Renders" on the other hand are very cheap if they don't actually touch the DOM.


In my experience watching for attribute changes is very fast; I suspect in part because you need to explicitly list out the attributes which you're watching for changes and it only triggers the attributeChangedCallback lifecycle hook if one of those specified attributes changes.

React renders aren't always cheap. It depends on the complexity of the component and you don't have as much control over the rendering. I've worked on some React apps which were so slow due to unnecessary re-renders that my new computer would often freeze during development. I was able to fix it by refactoring the code to be more specific with useState() (to watch individual properties instead of the entire object) but this flexibility that useState provides is a React footgun IMO. With HTMLElement, you're forced to think in terms of individual attributes anyway and these are primitive types like strings, booleans or numbers so there is no room for committing such atrocities.


>In my experience watching for attribute changes is very fast

Watching for attributes changing is not the whole picture. Why did the attribute change in the first place? Probably because you're doing stuff in terms of DOM attributes, which is slow - not individually, but in aggregate. Death by a thousand papercuts.

> I've worked on some React apps which were so slow due to unnecessary re-renders that my new computer would often freeze during development

I haven't managed to achieve that. If your computer freezes, that almost certainly means you're running out of RAM, not that your CPU is busy. I admit that the React "development build" overhead is considerable.

> I was able to fix it by refactoring the code to be more specific with useState() (to watch individual properties instead of the entire object) but this flexibility that useState provides is a React footgun IMO.

I admit that React has some footguns, but I don't see how the reactive model can be implemented entirely without them. It's a price I'm willing to pay, because it makes most things far easier to reason about. 90% of my components have no state at all. Of those that do, the vast majority have no complex state.


Basically the problem of any design by comittee stuff.


Supposedly more declaritive APIs and data binding are coming eventually. But who knows how long we will have to wait for that.


You don't really need all that stuff. Sanitization is straight forward to implement and only required for user generated strings (since you want to make it HTML-safe). It could be argued that automatically sanitizing everything including already safe data types like numbers and system-generated content adds an unnecessary performance overhead for certain projects.

As for events, binding is really very easy to do and it's already localised to the component so managing them is trivial.

Loops are also trivial; you can simply use Array.prototype.map function to return a bunch of strings which you can incorporate directly into the main component's template string. In any case, you can always use the native document.createElement and appendChild functions to create elements within the component and add them to its DOM or shadow DOM.

I've built some complex apps with plain HTMLElement as a base class for all my components and found is much simpler than React without any unexpected weirdness and using fewer abstract technical concepts. Code was much more readable and maintainable. I didn't even need a bundler thanks to modern async and defer attributes of script tags among others.

I think the reason why people are using React still is just marketing, hype and inertia. The job market which is gatekept by non-technical recruiters demands React. It's all non-tech people making the big decisions based on buzzwords that they don't understand.


> Sanitization is straight forward to implement

I would not say it's easy. Considering your adversaries are very motivated to do XSS and the web platform is very complicated.

> It could be argued that automatically sanitizing everything including already safe data types like numbers and system-generated content adds an unnecessary performance overhead for certain projects.

I don't think there's a substantial performance loss from doing a type check on a value to see that it's a number, and then putting it verbatim into the output (within your sanitization code).

I don't know what "system generated content" is, and I'd argue that neither does a framework. Which means the far safer route is to assume it came from a user by default and force the dev to confirm that it's not from the user.

> Loops are also trivial; you can simply use Array.prototype.map function to return a bunch of strings which you can incorporate directly into the main component's template string

Combined with the "it's fine" mentality on data sanitization, it's concerning that we're using the term "string" in relation to building DOM nodes here. I hope we aren't talking about generating HTML as strings, combined with default-trusted application data that in most applications, does in fact come from the user, even if you might consider that user trusted (because it's Dave from Accounting, and not LeetHacker99 from Reddit).


By "system generated content" I meant content which is not derived from potentially unsafe user input. For example, if your front end receives a JSON object from your own back end which was generated by your back end and contains numbers, booleans and enums (from a constrained set of strings) and it is properly validated before insertion into your DB, such data poses no risk to your front end in terms of XSS. That said, if you want to make your system fool-proof and future-proof, you could escape HTML tags in all your string data before incorporating it into a components' template string as a principle; such function is trivial to implement.

The main risk of XSS is when you inject some unescaped user-generated string into a template and then set that whole template as your component's innerHTML... All I want to point out is that not every piece of data is a custom user-generated string. Numbers, booleans don't need to be escaped. Error messages generated by your system don't need to be escaped either. Enum strings (which are validated at insertion in the DB) also don't really need to be escaped but I would probably escape anyway in case of future developer mistake (improper validation).

I agree that the automatic sanitization which React does is probably not a huge performance cost for the typical app (it's probably worth the cost in the vast majority cases) but it depends on how much data your front end is rendering and how often it re-renders (e.g. real time games use case).


> and it is properly validated before insertion into your DB, such data poses no risk to your front end in terms of XSS

This is making a lot of assumptions. Just because the data was acceptable in a database table does not mean it doesn't pose an XSS risk.

Bear in mind, in other branches of this discussion we're talking about using DOM text APIs to insert. Certainly that is a good, reliable way to avoid XSS, but you can consider that to be value sanitization just done for you by the browser. In the absence of that, advocating that "if it comes from the API it is safe" is a dangerous thing to advocate for.

The title "A world where <HTML> tag is not required for your web pages" might be perfectly valid to submit into your blog's CMS system, but that in no way means you can skip processing that in the frontend because "it is safe". Plenty of what you are saying is reasonable, but I think the topic requires a little more nuance in order to speak about the topic responsibly.


You get sanitization for free by using built in browser methods like setAttribute and textContent=.


Agreed, this is the safe approach if you create elements using document.createElement(). For cases where you want to generate some HTML as strings to embed within your component's template string (e.g. in a React-like manner using Array.prototype.map), you would have to escape the variables provided by the back end in case they contain HTML tags which could be used as an XSS attack.

Although such sanitization function is trivial to implement... In my previous comment, I mentioned using document.createElement() as a fallback if in doubt. It's safe to create the elements with the DOM API and using the textContent property as you suggest. That's why I don't see sanitization as a strong excuse to avoid using plain Web Components.


I agree that sanitization isn't an excuse not to use web components. Only that brushing off sanitization as solved by web components is dangerous rhetoric.


Of course, but I don't think the parent poster is talking about that.


Can you share an example?


Yeah, that's where I'm at, too. Authoring web components directly is too low level to be practical. You can easily end up reimplementing a ton of existing framework logic around rerenders, reactivity, event handlers, etc.

And there are libraries that handle all of this for you! But then you have tooling and non-standard syntax that puts you a lot closer to other JS frameworks.

And once you're in that space, the benefits of web components may or may not stack up against all the features and your team's pre-existing knowledge.


meanwhile Im like...

   <button onclick="innerHTML++">1</button>


GP comment shows a simple example, sure. And obviously it’s overkill just to implement incrementing a displayed value. But trivializing the example doesn’t reduce the “framework” to being pointless.


Meanwhile I’m like… That’s a Content Security Policy violation.


2 men are having lunch, one complaints that he always has cheese on his sandwiches. The other says, just ask your wife for something else? He responds: I always make it myself.

Content-Security-Policy: script-src 'self'; script-src-attr 'unsafe-hashes' 'sha256-9lIp1merGZMC6sfoM+OcgpSSRJJr18teLzyFangr0FY='


Web Components are very easy to write, here's an example Web Component: https://github.com/wisercoder/uibuilder/blob/master/WebCompo...

You can even use React-like templates. You need a 500-line lib to do that: https://github.com/wisercoder/uibuilder/tree/master


Thanks for sharing. Is there a non-golfed version of this that is understandable for grugbrained devs like me?


Thanks for sharing, we need more libraries like this. Even htmx.org is 150kb library over what was a few lines of xhr + el.innerhtml=response.result 10 years ago.

Where is the next wave of tiny libs that can make the web feel responsive again?


You could use `jsx` / `tsx`. Maybe it's worth for you to look at

https://github.com/cyco/WebFun/

Example for a `tsx` component:

https://github.com/cyco/WebFun/blob/master/src/ui/components...

Pure TypeScript / scss components.


That approach destroys and rebuilds the entire DOM on every render. This would not work for large compound components and apps.


I've always wondered why this notion is so popular (is it just because of what react does)? Wouldn't the native browser be expected to handle a DOM re-render much more efficiently than an entire managed JS framework running on the native browser and emulating the DOM? Maybe in 2013 browsers really really sucked at re-renders, but I have to wonder if the myriads of WebKit, Blink, Gecko developers are so inept at their jobs that a managed framework can somehow figure out what to re-render better than the native browser can.

And yes, I understand that when you program using one of these frameworks your explicitly pointing out what pieces of state will change and when, but in my professional experience, people just code whatever works and don't pay much attention to that. In the naive case, I feel like the browser would probably beat react or any other framework on re-renders every time. In the naive case where developers don't really disambiguate what state changes like they're supposed to. Are there any benchmarks or recent blogs/tech articles that dive into this?


I think the reason that the browser is so slow is that every time you mutate something, an attribute or add or remove an element, the browser rerenders immediately. And this is indeed slow AF. If you batched everything into a DocumentFragment or similar before attaching it to the DOM then it'd be fast. I don't know how you do that ergonomically though.


This is not true. The browser will only repaint when the event loop is empty. A bunch of synchronous DOM updates will result in one rerender.


It's partially true. Layout and repaint are two separate rendering phases. Repaint happens asynchronously, as you point out. But layout (i.e. calculating heights, widths, etc of each box) is more complicated. If the browser can get away with it, it will batch potential layout changes until directly before the repaint - if you do ten DOM updates in a single tick, you'll get one layout calculation (followed by one repaint).

But if you mix updates and reads, the browser needs to recalculate the layout before the read occurs, otherwise the read may not be correct. For example, if you change the font size of an element and then read the element height, the browser will need to rerun layout calculation between those two points to make sure that the change in font size hasn't updated the element height in the meantime. If these reads and writes are all synchronous, then this forces the layout calculations to happen synchronously as well.

So if you do ten DOM updates interspersed with ten DOM reads in a single tick, you'll now get ten layout calculations (followed by one repaint).

This is called layout thrashing, and it's something that can typically be solved by using a modern framework, or by using a tool like fastdom which helps with batching reads and writes so that all reads always happen before all writes in a given tick.


Modern browsers simply set a bit that may lead to repaint after the next layout pass. Things have changed a bit since React was released in 2013.


> I think the reason that the browser is so slow is that every time you mutate something, an attribute or add or remove an element, the browser rerenders immediately.

Is it really immediately? I thought that was a myth.

I thought that, given toplevel function `foo()` which calls `bar()` which calls `baz()` which makes 25 modifications to the DOM, the DOM is only rerendered once when foo returns i.e. when control returns from usercode.

I do know that making changes to the DOM, when immediately entering a while(1) loop doesn't show any change to the DOM.


Yes and no.

The browser will, as much as it can, catch together DOM changes and perform them all at once. So if `baz` looks like this:

    for (let i=0; i<10; i++) {
      elem.style.fontSize = i + 20 + 'px';
    }
Then the browser will only recalculate the size of `elem` once, as you point out.

But if we read the state of the DOM, then the browser still needs to do all the layout calculations before it can do that read, so we break that batching effect. This is the infamous layout thrashing problem. So this would be an example of bad code:

    for (let i=0; i<10; i++) {
      elem.style.fontSize = i + 20 + 'px';
      console.log(elem.offsetHeight);
    }
Now, every time we read `offsetHeight`, the browser sees that it has a scheduled DOM modification to apply, so it has to apply that first, before it can return a correct value.

This is the reason that libraries like fastdom (https://github.com/wilsonpage/fastdom) exist - they help ensure that, in a given tick, all the reads happen first, followed by all the writes.

That said, I suspect even if you add a write followed by a read to your `while(1)` experiment, it still won't actually render anything, because painting is a separate phase of the rendering process, which always happens asynchronously. But that might not be true, and I'm on mobile and can't test it myself.


> Now, every time we read `offsetHeight`, the browser sees that it has a scheduled DOM modification to apply, so it has to apply that first, before it can return a correct value.

That makes perfect sense, except that I don't understand how using a shadow DOM helps in this specific case (A DOM write followed immediately by a DOM read).

Won't the shadow DOM have to perform the same calculations if you modify it and then immediately use a calculated value for the next modification?

I'm trying to understand how exactly a shadow DOM can perform the calculations after modifications faster than the real DOM can.


The shadow DOM doesn't help at all here, that's mainly about scope and isolation. The (in fairness confusingly named) virtual DOM helps by splitting up writes and reads.

The goal when updating the DOM is to do all the reads in one batch, followed by all the writes in a second batch, so that they never interleave, and so that the browser can be as asynchronous as possible. A virtual DOM is just one way of batching those writes together.

It works in two phases: first, you work through the component tree, and freely read anything you want from the DOM, but rather than make any updates, you instead build a new data structure (the VDOM), which is just an internal representation of what you want the DOM to look like at some point in the future. Then, you reconcile this VDOM structure with the real DOM by looking to see which attributes need to be updated and updating them. By doing this in two phases, you ensure that all the reads happen before all the writes.

There are other ways of doing this. SolidJS, for example, just applies all DOM mutations asynchronously (or at least, partially asynchronously, I think using microtasks), which avoids the need for a virtual DOM. I assume Svelte has some similar setup, but I'm less familiar with that framework. That's not to say that virtual DOM implementations aren't still useful, just that they are one solution with a specific set of tradeoffs - other solutions to layout thrashing exist. (And VDOMs have other benefits being just avoiding layout thrashing.)

So to answer your question: the virtual DOM helps because it separates reads and writes from each other. Reads happen on the real DOM, writes happen on the virtual DOM, and it's only at the end of a given tick that the virtual DOM is reconciled with the real DOM, and the real DOM is updated.


I'm gonna apologise in advance for being unusually obtuse this morning. I'm not trying to be contentious :-)

> So to answer your question: the virtual DOM helps because it separates reads and writes from each other. Reads happen on the real DOM, writes happen on the virtual DOM, and it's only at the end of a given tick that the virtual DOM is reconciled with the real DOM, and the real DOM is updated.

I still don't understand why this can't be done (or isn't currently done) by the browser engine on the real DOM.

I'm sticking to the example given: write $FOO to DOM causing $BAR, which is calculated from $FOO, to change to $BAZ.

Using a VDOM, if you're performing all the reads first, then the read gives you $BAR (the value prior to the change).

Doing it on the real DOM, the read will return $BAZ. Obviously $BAR is different from $BAZ, due to the writing of $FOO to the DOM.

If this is acceptable, then why can't the browser engine cache all the writes to the DOM and only perform them at the end of the given tick, while performing all the reads synchronously? You'll get the same result as using the VDOM anyway, but without the overhead.


No worries, I hope I'm not under/overexplaining something!

The answer here is the standard one though: if you write $FOO to DOM, then read $BAR, it has to return $BAZ because it always used to return $BAZ, and we can't have breaking changes. All of the APIs are designed around synchronously updating the DOM, because asynchronous execution wasn't really planned in at the beginning.

You could add new APIs that do asynchronous writes and synchronous reads, but I think in practice this isn't all that important for two reasons:

Firstly, it's already possible to separate reads from writes using microtasks and other existing APIs for forcing asynchronous execution. There's even a library (fastdom) that gives you a fairly easy API for separating reads and writes.

Secondly, there are other reasons to use a VDOM or some other DOM abstraction layer, and they usually have different tradeoffs. People will still use these abstractions, even if the layout thrashing issue were solved completely somehow. So practically, it's more useful to provide the low-level generic APIs (like microtasks) and let the different tools and frameworks use them in different ways. I think there's also not a big push for change here: the big frameworks are already handing this issue fine and don't need new APIs, and smaller sites or tools (including the micro-framework that was originally posted) are rarely so complicated that they need these sorts of solutions. So while this is a real footgun that people can run into, it's not possible to remove it without breaking existing websites, and it's fairly easy to avoid if you do run into it and it starts causing problems.


As I understand it, there's a couple of issues with naively rerendering DOM elements like this.

Firstly, the DOM is stateful, even in relatively simple cases, which means destroying and recreating a DOM node can lose information. The classic example is a text input: if you have a component with a text input, and you want to rerender that component, you need to make sure that the contents of the text input, the cursor position, any validation state, the focus, etc, are all the same as they were before the render. In React and other VDOM implementations, there is some sort of `reconcile` function that compares the virtual DOM to the real one, and makes only the changes necessary. So if there's an input field (that may or may not have text in it) and the CSS class has changed but nothing else, then the `reconcile` function can update that class in-place, rather than recreate it completely.

In frameworks which don't use a virtual DOM, like SolidJS or Svelte, rerendering is typically fine-grained from the start, in the sense that each change to state is mapped directly to a specific DOM mutation that changes only the relevant element. For example in SolidJS, if updating state would change the CSS class, then we can link those changes directly to the class attribute, rather than recreating the whole input field altogether.

The second issue that often comes with doing this sort of rerendering naively is layout thrashing. Rerendering is expensive in the browser not because it's hard to build a tree of DOM elements, but because it's hard to figure out the correct layout of those elements (i.e. given the contents, the padding, the surrounding elements, positioning, etc, how many pixels high will this div be?) As a result, if you make a change to the DOM, the browser typically won't update the DOM immediately, and instead batches changes together asynchronously so that the layout gets calculated less often.

However, if I mix reads and writes together (e.g. update an element class and then immediately read the element height), then I force the layout calculation to happen synchronously. Worse, if I'm doing reads and writes multiple times in the same tick of the Javascript engine, then the browser has to make changes, recalculate the layout, return the calculated value, then immediately throw all the information away as I update the DOM again somewhere else. This is called layout thrashing, and is usually what people are talking about when they talk about bad DOM performance.

The advantage of VDOM implementations like React is that they can update everything in one fell swoop - there is no thrashing because the DOM gets updated at most once per tick. All the reads are looking at the same DOM state, so things don't need to be recalculated every time. I'm not 100% sure how Svelte handles this issue, but in SolidJS, DOM updates happen as part of the `createRenderEffect` phase, which happens asynchronously after all DOM reads for a given tick have occurred.

OP's framework is deliberately designed to be super simple, and for basic problems will be completely fine, but it does run into both of the problems I mentioned. Because the whole component is rerendered every time `html` is called, any previous DOM state will immediately be destroyed, meaning that inputs (and other stateful DOM elements) will behave unexpectedly in various situations. And because the rendering happens synchronously with a `innerHTML` assignment, it is fairly easy to run into situations where multiple DOM elements are performing synchronous reads followed by synchronous writes, where it would be better to do all of the reads together, followed by all of the writes.


Thanks for the info! This all makes sense to me intuitively, but I know I've been bitten in the butt several times by implementing clever caching schemes or something that end up slowing the app down more than I thought it would speed it up. It seems like it would be simple enough to set up a test case and benchmark this (you wrote a simple for loop above that should exhibit this behavior). I'm curious how much, if any, react actually ends up saving cycles when a programmer does the same code naively in react and naively in the browser. I think it would make for some interesting benchmarks at least :)


My question is why don't more frameworks use it, especially the more popular ones.


Frankly, web components aren't great.

I am using them, because I've landed in a few projects where adding a JS build step is too heavyweight for very minimal JS needs. But here are my gripes:

1. Templates are way too complicated to be useful. I have no idea what they were thinking here.

2. The shadow DOM not inheriting styles means I effectively can't use the shadow DOM for anything useful. My ideal use case would be that I could child elements to the current component into the shadow DOM. This allows me to do (for example) things like have a <tab-area> where the user can define <tab-> child elements which in turn can contain arbitrary content: when the user selects a tab I copy the contents of the <tab-> tag into the shadow DOM so that that's what becomes visible. But if I do that, the element content becomes unstyled. There are a bunch of really useful elements that are just completely blocked by this poor design choice. As a result, I just don't use the shadow DOM, but then I have to mutate the tabs to make them visible in place, and that means changes to the HTML of tabs can potentially cause issues for my <tab-area> and vice versa.

The first is a missed opportunity, but it's fairly easy to just not use them. The second problem is one of the worst API decisions I've ever seen, which would have been detected very quickly if anyone designing this API had been arsed to try to use their own API.


The shadow DOM can inherit styles if you specify the styles that you want to inherit. Additionally, web components work just fine without the shadow DOM, it's optional, but for a great many custom elements you don't want them inheriting all the styles, because that can break the custom element.


> The shadow DOM can inherit styles if you specify the styles that you want to inherit.

Ah yes, I've heard about this "mixing CSS into your HTML".

The entire point of CSS is that you can write selectors that can affect anything, import it in the header, and you're done. If you're telling me I have to import a new CSS file (or repeat myself and import the same CSS file) inside of every custom element I create, obviously I thought of that. I'm telling you that's a terrible, awful idea, because it breaks the fundamental way CSS is supposed to work.

Consider: if I'm distributing a library of web components, which of my users' CSS files that I know nothing about do I include? Did you read the example I already gave? The solution you're offering doesn't solve the problem I've described.

> Additionally, web components work just fine without the shadow DOM, it's optional,

You should read the post you're responding to in order to find out why that also causes problems.

> but for a great many custom elements you don't want them inheriting all the styles, because that can break the custom element.

...just like all of the rest of CSS. We have strategies for dealing with that. The only strategy you've suggested for doing the opposite is a completely useless non-solution.


> Consider: if I'm distributing a library of web components, which of my users' CSS files that I know nothing about do I include?

If you are distributing a library of web components, wouldn't you provide a CSS api for the things that are supposed to be styleable via CSS custom properties and styleable parts?

Case in point: consider Shoelace.

> a completely useless non-solution

Consider the existing libraries of web components — Shoelace for something generic, RedHat's Patternfly or Adobe's Spectrum for something company-specific. How much of a non-solution are they, really?


> If you are distributing a library of web components, wouldn't you provide a CSS api for the things that are supposed to be styleable via CSS custom properties and styleable parts?

If you read my previous comments you'll see that the parts that should be styleable--i.e. the content of the component I've defined--are user-defined, so no, I can't document them.

> Consider the existing libraries of web components — Shoelace for something generic, RedHat's Patternfly or Adobe's Spectrum for something company-specific. How much of a non-solution are they, really?

If you read what I said in context, I'm not saying components are useless, I'm saying the unstyled shadow DOM is a completely useless non-solution to the problem I've described.


> I'm telling you that's a terrible, awful idea, because it breaks the fundamental way CSS is supposed to work.

The "way CSS is supposed to work" was always a bad idea and is totally unworkable for large projects / teams. Throwing it out and simply inlining all your styles is absolutely the right call.


Eh, there's some validity to that, but the worst of both worlds is to have both a CSS file and then a bunch of inline styles, which is what people here are proposing.

Inline styles make a lot of sense if you're using webcomponents pervasively, but there's a high up-front cost to doing that because you'll have to either a) define web components for everything you might want to style, including existing elements like <p>, or b) repeat yourself a lot.


Without the shadow dom, though, what really is the difference from just using `<div>` elements? You can't use slots, you're vulnerable to bad `querySelector`s, events aren't isolated, and you get none of the performance benefits.

I've tried to find a workable solution using style inheritance, but it doesn't work everywhere. On code sandbox sites like codepen.io, the stylesheet is generated for you and sometimes updated in-place. You'd have to watch with a MutationObserver and then propagate the change to every instance of a custom element on the page.


> You'd have to watch with a MutationObserver and then propagate the change to every instance of a custom element on the page.

This solution works in that you can create a mixin that does this and use it in your custom elements. But if you profile it I think you'll discover it's painfully slow. It's not noticeable if you're using a few custom elements here and there, but if you're, for example, making a table of custom elements, the page will load slowly.


Yeah, that's exactly my experience. I built a framework for quickly prototyping with web components before I discovered the CSS-isolation defect. Sadly it's pretty much killed that idea as unworkable.

<https://codepen.io/webstrand/pen/jOzYVpL> is as far as I got. The FOUS is pretty annoying, too, before the templates get properly registered. But I could live with that.


> Without the shadow dom, though, what really is the difference from just using `<div>` elements?

Having a lifecycle that you can hook into to know when to run code related to this particular instance of the custom element.

See e.g. a recent talk on web components — https://youtu.be/jBJ7eoPtmY8?t=367


probably what GP meant there is now way to inherit styles without specifing which styles you want to inherit. There should option shadow dom inherit all styles.


it sounds like the thing you are looking for are slots and they are supported in shadow DOM with the HTMLSlotElement


No, because then the user of the custom element has to put slot attributes on the things they put in the tabs. Consider this:

    <tab-area>
      <tab- text='First'>
        ...arbitrary HTML that should work intuitively, i.e. be styled
        like the rest of the document.
      </tab->

      <tab- text='Second'>
        Note the lack of slot attributes on these tab elements. That's
        because we want this to behave like normal HTML where you
        can nest without having to worry about how tab-area was implemented.
        Why should we have to care that tab-area was implemented with
        templates/slots?
      </tab->

      <tab- text='Third'>
        There could be any number of these tabs so you can't put them
        into slots without some sort of late-generating the slots anyway.
      </tab->
    </tab-area>
We want this to render like a group of tabs where "First" and its body are visible, and tabs are peeking up from behind with the texts "Second" and "Third" but the content of those tabs isn't shown until you click the tab (at which point the content of "First" is hidden).

That's not achievable with slots.

In general, I haven't found a case where templates/slots are actually useful--it's a lot of work to create a bad interface.


Most of the time you have the tab and the panel to display the content of the tab a super simple demo implementation of something like this https://web.dev/components-howto-tabs/

In regards of slots, they are a nice way to allow a user of you webcomponent to overwrite/replace parts of your component with something else. This is most of the time a more advanced feature and I find it quite nice to build headless components


> Most of the time you have the tab and the panel to display the content of the tab a super simple demo implementation of something like this https://web.dev/components-howto-tabs/

That's exactly what I'm saying is bad. Why does the user of the howto-tabs have to type "slot='panel'" when that information is already communicated by the fact that it's a "howto-panel" inside a "howto-tabs"? This is a useless leaking of implementation details.

And that page just keeps going and going. Sure, some of that is because they're implementing a few neat features, but most of it is because slots are way overcomplicated for something that's actually easier to do without them.


you can just have an unnamed slot. I'm probably missing something here haven't build a tabs thing in a long time. But just trying this out for a couple of minutes the thing you want seems to be possible. I didn't do any styling here and the code is just something I hacked down to test it. Where hn-tabs is your tab-area and hn-tab your tab-

   class HnTabs extends HTMLElement {
       constructor() {
           super();
           this._tabs = {}
           this.attachShadow({ mode: 'open' })
           const ul = document.createElement('ul')
           this.shadowRoot.appendChild(ul)
           const slot = document.createElement('slot')         
           this.shadowRoot.appendChild(slot)
           slot.addEventListener('slotchange', () => {
             ul.innerHTML = ''
             const tabs = Array.from(this.querySelectorAll('hn-
             if (!tabs.some((tab) => tab.hasAttribute('active')
               tabs[0].setAttribute('active', '')
             }
             tabs.forEach((tab) => {
               const li = document.createElement('li')
               li.innerText = tab.getAttribute('text')
               ul.appendChild(li)
               li.addEventListener('click', (e) => {
                 tabs.forEach((t) => t.removeAttribute('active'
                 tab.setAttribute('active', '')
               })
             })
           })
       }
   }
   customElements.define('hn-tabs', HnTabs);
   
   class HnTab extends HTMLElement {
       static get observedAttributes() {
         return ['active'];
       }
       constructor() {
           super()
           this.attachShadow({ mode: 'open' });
           this._slot = document.createElement('slot')         
           this.shadowRoot.appendChild(this._slot)
           this._slot.style.display = 'none'
           console.log(this.slot)
       }
       attributeChangedCallback() {
         this.#activeStatus()
       }
       connectedCallback() {
         this.#activeStatus()
       }
       #activeStatus(){
         const active = this.hasAttribute('active');
         this._slot.style.display = active ? 'block' : 'none'
       }
   }
   customElements.define('hn-tab', HnTab);


When I have some time I'll play around with this: I'm certainly open to the idea that there's some stuff I don't know about slots. But I have a question: When you append the slot to the shadow root, doesn't that mean that you lose styling in the slot contents?


no things inside slots keep the styling from the outside


Hm, I stand by my comment that slots are overcomplicated, but that might actually make it worth it.


Okay, I got around to trying this out, and that's cool. Thanks for explaining this.


you are welcome happy to help.


Unless you are still in the IE world (which is challenging when using WCs), use one `append` instead of multiple `appendChild`


use: slotAssignment: 'manual'


My hot take: they're a pain in the ass to use for very much. Compared to frameworks in userland, they're cumbersome and don't add value. Frameworks make other things better (besides the problems web components solve), making web components an implementation detail. If you're using React, you have no reason to consider web components.

In my entire career, I've had one use case that web components were perfect for: framework agnostic component interfaces. I specced the use case for a product at Stripe [0] and it was wildly successful. What it solved was avoiding the need to ship a library for each framework to embed our components: you just used HTML. Web components are perfect for this, because they are the lowest common denominator that everyone supports. Even if you're not using a framework, you can still embed the components with no real effort. That's the best I can say about web components, though.

[0] https://stripe.com/docs/connect/get-started-connect-embedded...


It's been a while, and overall I'd like them to succeed, but here are the first issues I remember.

The fact that scoped elements are not yet part of the standard is quite a turn off. Every web component is global and the first one that uses a name wins. Even if you are not competing with third party libraries you will need to have multiple versions of your same component very soon to handle breaking changes.

The string only passing is an headache when you'd like to pass object/functions to compare later on.

The fact that some CSS properties can bleed in the shadow Dom, while others don't.

To introduce them into an existing codebase you need support, and last time I checked the support from React was clunky


I've been able to piece-meal RiotJS components into an existing code base pretty easy.

Tools like React, etc al, requires almost doing everything that way.

RiotJS fits in for adding a bit to "old-school" web-apps (PHP, Ruby, Perl, ASP3).

Legacy backend, slightly more modern frontend.


I've been using web components now for years and I will do everything I can to never write frontends in anything else. I totally agree with point 2 though: the Polymer phase was very weird, but, as mentioned, https://lit.dev is great and, imo, the perfect abstraction.


Lit seems functionally equivalent to Svelte. Might Lit only be better if you have a preference towards declaring classes? Perhaps tracing back to Backbone or React before hooks?

Svelte supports custom elements: https://svelte.dev/docs/custom-elements-api


Lit uses standard languages, doesn't require a compiler, is faster, and is smaller as you amortize the library size over a collection of elements. Those are real differences.


> uses standard languages, doesn't require a compiler

It recommends a build step, but yes, you can use it without one. Also, TypeScript isn't a standard language. So if you're using .ts you may as well use .svelte.

> is faster

I don't know that Svelte has been benchmarked with custom elements. Either way it's plenty fast.

> is smaller as you amortize the library size over a collection of elements

That's kind of a funny thing to worry about. It has improved in Svelte 4, though. https://svelte.dev/blog/svelte-4 I also don't know that Lit is so small in practice. With Lit you get to use map and the ternary operator and all that jazz, like React. https://lit.dev/docs/templates/conditionals/


When did @click and so on become part of HTML?


If Lit is equivalent to Svelte, then BASIC was equivalent to COBOL


Would love to peruse a couple example repos for learning if you can share some public links.


Not parent, but I wrote some components based on lit-js, they're here: https://git.sr.ht/~mariusor/oni/tree/master/item/src/js. The top level call would be oni-main-actor.render()

However I am a total noob so it might not be entirely idiomatic. An example for the end result is at https://releases.bruta.link


Lit is so good. It’s just enough extra on top of native web components - to be useful, but without feeling like it stacks a bunch of extra bullshit. I really wish more teams were into it.


It looks good. But it is a Google project, and with Google's DRM attack on the web I don't know if I'm willing to give these more traction by being among the user base.


lit looks like someone's trying to make a GUI in java, what is the need/deal with these annotations etc?


IMO the main reason more people aren’t using Web Components is because React doesn’t support them.

Preact does it just fine so I have to assume it’s possible but simply not in the interests of the React team. React feels like the enterprise lock-in of 2020s development: if you use it you’re using it top to bottom and it’s very difficult to mix and match with other frameworks. It’s a shame.


Nobody (meaning very few) care for Web Components, React or not.

Even in frameworks that do support them, they're just not widely used.


Web Components are just an API, a means to an end. You might as easily say nobody cares for document.createElement() but it’s still used by underlying code all the time.

If it were much easier to mix and match components that use different frameworks then people would be using web components whether they know it or not. But we’re in a React monoculture and React folks seem quite content with that.


mm...

Web components aren't 'an' api, it's a specific way of using a set of apis, and specifically requires you to use a set of templates in a way that frameworks (which looovvvveeee DSLs) won't accept.

The other bits? shadow dom? Custom elements? eh. Those are things a framework will wrap happily enough if they see value in it. Maybe some already do? As you say, you'd never know...

...but those html templates will never fly. They're just crazy bad ergonomics and framework authors will never accept them.

That's really the core of the issue; invoking components in your layout requires you to use some kind of layout template, and every framework does it differently.

That's why cross framework css solutions (eg. tailwind) are massively successful, because they can be called easily by anyone regardless of the templating; the same is not... and, frankly, seems like it never will, be true of web components.


They shouldn't be called web components in React (typical JS world term squatting). Instead they should be called React Components.


I personally think more people aren't using WC because React simply exists. I know that sounds controversial, but it isn't.

https://2022.stateofjs.com/en-US/libraries/


Where do you see React doesn’t support web components?.. also many frameworks have wrappers around web components specifically for frameworks like Vue and React for things like passing other values than strings.


In Angular and Vue, their component model nowadays can be mapped into web components directly, it is only a matter of configuration.

React pretends they don't exist.


React supports Web Components, just some quirks to be aware of: https://custom-elements-everywhere.com/


Web components fill an important niche: creating a library of components that can be easily used in react, angular, some other framework, no framework, or some framework that someone will invent next year. I expect their usage to increase over time.


Modern JS frameworks are heavily mixing mostly static with limited amounts of interactive components but all in one system. Where everything can still be the same component architecture, the same UI systems, same CSS/JS packages, same build systems, etc but you selectively choose which components (or small sections of a page) are interactive and require hydration.

I don't really see how Web components fill that picture as cleanly. You would basically still forced to choose between component overhead/custom code OR static HTML, creating a formal discintion rather than a boolean or flag or whatever. Why create that distinction just because it's "native" to the browser? The DOM is sufficient when your default is static and components are always SSR in the same DOM model.

It basically only makes sense if you're not building a "holistic" single frontend framework driven site and rather are mixing various server side view systems and occasionally adding a couple components.

Basically the sort of thing better suited for major legacy sites like Google rather than something you'd ever choose from the ground up.


This is a half truth. Their are big problems with this approach, as I think a lot of folks are finding out the hard way, namely:

- You cede rendering control from your framework to the web component, if it does anything like conditional children etc. meaning it can be hard to optimize or you get stuck using refs, which is suboptimal and in some frameworks makes optimizations hard to achieve

- it’s de facto CSS in JS and to boot your styles are also locked to shadow dom. Yes I know this isn’t true in an absolute sense but in practice all the web component implementations leverage this. This makes styling harder, can have performance impacts etc. also means you can’t leverage CDN caching and distribution for your styles

- FOUC is a real problem with web components as it exists today.

- Interop varies and can have gotchas, especially with custom events that may be emitted by components

- You can’t get fine grained optimizations in some frameworks using them because web components in practice often contain some kind of logic one way or another.

- to use slot you must use shadow dom, and that means your app needs to be rendering against a shadow root of some sort, which can be real clunky


I feel the opposite. I feel web components will hit the ceiling far faster due to the reduced churn and because their use case isn't all that applicable to most. Also why we see that a lot of the adoption of WC comes from major organizations with heavy design systems, those who do need to make components work across frameworks due to internal separation. Whereas most smaller teams or solo developers won't be involving themselves in that process at all.

It's a solid stable niche, not a growth kind of niche.


Too little too late.

My bet is WASM being the Great Leap Forward because everything else that has been done in JavaScript is just layer on layer of abstraction, obfuscation and horrendous complexity.

I would rather develop in a mature language and have that transpiled to WASM rather than fight the frameworks (I still remember when Angula 5 was a 15,000 file desktop mom install !).

JavaScript has fulfilled an important role as the COBOL of the web. Low barrier to entry - anybody can do something useful without education or expense.

Now the web needs move forward to become the first choice desktop. That will take tools that can really do the whole stack, like C, Go, Java, Zig, Virgil productively.

Industry has been busy on the JS bandwagon and adding vast amounts of complexity to browsers and web infrastructure when maybe (happy for opinions to be voiced on this) we should be just adding more language runtimes to browsers.


How would WASM alleviate any of the issues that has led to complex frameworks in JS-land?


The nice thing is that if you're using a library like Lit or Svelte, you won't have to bother with the web component API much at all. https://svelte.dev/docs/custom-elements-api https://lit.dev/


Web components are doing really well: they're used on > 18% of Chrome page views, huge apps like Photoshop, parts of Chrome, Chrome OS, and Firefox, more and more of Reddit, and tons of design systems are built with them, and Lit has far more weekly npm downloads than Svelte or Solid.


We're a fairly large , publicly traded org that no one has ever heard of but almost everyone interacts with our products. All our frontend products are entirely web-components (Lit). I like them. Used stencil before this at another job and it was also just fine.


This article doesn't really follow the title? I was expecting some critique of existing software and why someone prefers alternatives. Instead, I got a list of excuses on why people haven't really looked at it.

Not that any of the points are bad. As read, they all seem like solid reasons to not use something. Indeed, the fact that frameworks have no reason to use these over user space constructs seems very compelling, all on its own.

The rest feels like a thinly veiled rewording of the second point? That is, this feels like advocacy that is blaming the messengers for not successfully convincing us that we should use this superior thing. Which... is still a heavy handed advocacy. :(


Google teached me over the years that they can deprecate or remove support from one day to another, and I don't matter to them, neither as user, developer, or company owner.

So even if I love web components since it got released (and have used them in some toy apps) I can't count on them yet thanks to Google.

You can't trust anything coming from Google. There is no assurance that they announce tomorrow that they remove support for them in chrome. Or even worse, they will remove support in 6 month (killing all the developers market and destroying any production app). To announce in 3 years that they will continue the effort of removing them...

It's just sad. </Rant>


good article except for the firefox shade


I thought so too, and tried to dig into the specifics.

I ended up here:

https://hacks.mozilla.org/2014/12/mozilla-and-web-components...

And left very unsatisfied that this is the current answer, only because I'd like to better understand how the use of JS modules over time has played out wrt html imports.


Things like HTML (and JSON) imports in ES modules, among other things, have been waiting on some safety signalling mechanics currently named "Import Attributes". Import Attributes are currently in Stage 3 [0].

The basic security story is that browsers never care about file extensions, they care about MIME types. A developer might add an import to a third-party HTML or JSON file somewhere and expect one "safe" behavior, but the third-party could just return a MIME type of "text/javascript" and inject an entire script and the browser is supposed to respect that MIME type.

To keep things safe, browsers want a way to signal that an import is supposed to JSON (or HTML or CSS) rather than JS and error if it gets back something "wrong" from a server request. That's one of the proposed uses for Import Attributes to suggest expected MIME types for non-JS modules in ES module imports.

Unfortunately, there are other proposed uses for Import Attributes (things like including hashes for integrity checks) and so there have been quite a few revisions (and multiple names) for Import Attributes trying to best support as many of the proposed uses as possible, and that has slowed progress on it a lot more than some people would wish.

[0] https://github.com/tc39/proposal-import-attributes


Thank you, interesting!


Yeah I’m always surprised to hear of Dave’s commitment to Chrome via Shoptalk Show commentary.


I'm not using them because WebKit (i.e. Apple), in a long-standing vendor pissing contest¹ they've tunnel-vision'd themselves into, still refuses²³ to implement the customized built-in elements⁴ part of the standard, without which the content model for HTML cannot be fully realized (e.g. custom elements inside a table), and to make matters worse, without offering a firm proposal or implementation of an equivalent alternative.

(and this is something a polyfill can only partially bridge)

[1] https://github.com/WICG/webcomponents/issues/509#issuecommen...

[2] https://bugs.webkit.org/show_bug.cgi?id=182671

[3] https://github.com/WebKit/standards-positions/issues/97

[4] https://html.spec.whatwg.org/multipage/custom-elements.html#...



Web components are extremely useful even without customized built-ins. And Apple is open to alternatives.


I don't disagree about the first part, but it is nevertheless the reason I'm not using them, since I have a very general use case (c.f. https://github.com/hotwired/turbo/pull/131).

As for representing Apple's state of mind w.r.t. alternatives; they've had the better part of a decade to shit or get off the pot, so I don't hold a shred of vendor sympathy. In that context, "open to alternatives" just sounds like product manager code for "we have no ideas of our own" and "let's kick the can down the road as much as we possibly can". If anything riles me up, it's the disinterest in developing a substitute capability, and if Apple weren't a steward of the standard it'd matter far less. I'll compare & contrast Cisco's running battles over their own PoE vs the IEEE802.3 standards that they'd had a hand in developing; at least the vendor offered a concrete alternative.


I don't think Apple sees themselves as _needing_ to shit or get off the pot. They made their opposition to customized built-ins, and their reasoning, known from the beginning, and Chrome and Firefox proceeded anyway.

From Apple's point-of-view they're probably waiting for someone to propose a solution that doesn't have the same perceived issues. They've been doing good work on declarative shadow DOM, selection, a11y, template instantiation, etc., in the mean time.


> I don't think Apple sees themselves as _needing_ to shit or get off the pot.

Indeed, judging from both words & (in)actions, I think we can safely conclude that they don't. There's likely no direct commercial pressure to implement this part of the standard, and I'd expect all members of WHATWG's steering group perceive the vast influence they consequently hold as a competitive advantage, not a social contract.


I don't quite understand that last bit. Apple objected to this part of the standard, but since WHATWG doesn't work on a consensus model like TC39, they don't have an official way to block. So they registered their rather strong objection - including that they have no intentions to implement it - the feature got merged over their objections, and everyone moved on with the rest of the specs.

I'm not sure what competitive advantage that gains them or anyone else. There's an impasse, and effectively no one uses this feature.


I use custom elements directly in a bunch of stuff I had roll and I find the composition and reuse advantage nice. I tend to not bother with template elements, they don’t help much. I also don’t bother with shadow dom or element styles, as again, until you are making a mess with a distributed team you don’t actually have css problems. css also got a lot better at not causing problems, with fancier selectors and less problematic layout models.

People say they’re slow, but things I’ve hand rolled this way, while none are super complex, I can make thousands of elements and not put a dent in any metrics or lighthouse runs. I tend to bind stuff I’m going to manipulate in the child tree to variables in the constructor, so I never really have the cost of constant lookups, which maybe amortizes well once you’re looking at whole program performance.


I wouldn't mind a sanity check on using Web Components in this context:

We have a bunch of sites written in different frameworks (React, Rails + Hotwire, Phoenix + Liveview) that we'd like to have a shared set visual components between them. Think things like:

- Buttons - Text spacing - Layout cards - Headers

Pretty low level stuff, the most dynamic behaviour might be something like showing / hiding content in an FAQ.

Web Components seems like a reasonable fit to encapsulate the design elements and reuse them across all these different frameworks (probably coupled with Tailwind CSS on each site to enforce consistent colours + spacing).


Web components are really only useful for stuff that's dynamic, like a sortable table that fetches data from an AJAX request. They require javascript and have no fallback or graceful degradation in functionality at all, which means using web components for all your bog standard buttons, text content, headers, etc. could be a very bad idea (not to mention it would trash the SEO and potentially accessibility of your site).

IMHO you probably want to look at design tokens or other ways to centralize your visual styles--it's really more of a workflow problem and not just something to throw more javascript at with web components.


> we'd like to have a shared set visual components between them

I think what gp wants is css!


Well, ideally css which belongs to the component that is using it. CSS modules and other encapsulation techniques have been pretty beneficial. I'd like to keep that going vs. trying to manage the "cascading" part of stylesheets over multiple sites.


> They require javascript and have no fallback or graceful degradation in functionality at all

This is false. Use <template> and <slot>. First-class progressive enhancement.


Ehh slots have issues too, particularly that if you use them they punch through and totally ignore your shadow DOM to inherit the main body styles. Sometimes you want this behavior and sometimes you don't, but either way you only get it and have no control over the elements passed in the slots. They defeat the whole point or just greatly complicate the idea of encapsulating your element's visuals in one component.

I'm truly skeptical there is much to be gleaned accessibility-wise from templates if JS is disabled. There's no way to know how and where the template was meant to be instantiated in the DOM and accessibility tree without JS.


Have you ever seen an example of this in the wild?


Sometimes, but not enough. Everyone tries to write web components like it's "Open Standards React Lite". And the results are as poor as expected.

Web components are superficially similar to React/Angular/Vue, but very different in practice. Different strengths. Different use cases. Different opportunities. Like the post says, the web components community has been terrible at articulating and marketing these differences.


Web components are treated like HTML elements. You can nest and combine them with semantic elements and attach attributes to them.

Say you have a component that does something with titles (h1 etc.), then you would wrap the semantic element with a web component.

You do the same with buttons, text or input fields etc.

This way you 100% retain the semantic structure (before loading JS). And it’s possible to progressively enhance the DOM if that is a goal.


The main limitation of web components for me is that all data needs to be passed to the component attributes as strings. Wish we could pass objects and arrays without JSON.stringify-ing them.


Because React is good enough. I think that's mostly why.

Here's legacy React's like_button.js, without JSX, from their old documentation:

    'use strict';

    const e = React.createElement;

    class LikeButton extends React.Component {
      constructor(props) {
        super(props);
        this.state = { liked: false };
      }

      render() {
        if (this.state.liked) {
          return 'You liked this.';
        }

        return e(
          'button',
          { onClick: () => this.setState({ liked: true }) },
          'Like'
        );
      }
    }

    const domContainer = document.querySelector('#like_button_container');
    const root = ReactDOM.createRoot(domContainer);
    root.render(e(LikeButton));
Here's the equivalent using the Web Components specification[1]:

    // https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements
    // Create a class for the element
    class LikeButton extends HTMLElement {
      static get observedAttributes() { return ['liked']; }

      constructor() {
        // Always call super first in constructor
        super();

        this.liked = false;

        // Create a shadow root
        /* const shadow = */ this.attachShadow({mode: 'open'});
      }

      get liked() { return this.hasAttribute('liked'); }
      set liked(state) { this.toggleAttribute('liked', Boolean(state)); }

      attributeChangedCallback(name, oldValue, newValue) {
        this.render();
      }

      connectedCallback() {
        this.render();
      }

      render() {
        const shadow = this.shadowRoot;

        if (this.liked) {
          shadow.innerHTML = 'You liked this.'
          return;
        }

        shadow.innerHTML = `<button onclick="this.parentNode.host.liked = true;">
      Like
    </button>`;
      }
    }

    // Define the new element
    customElements.define('like-button', LikeButton);
The React API is slightly nicer to use, especially with JSX.

[1]: https://github.com/andrewmcwatters/custom-elements


Can be reduced

    customElements.define('like-button', class LikeButton extends HTMLElement {
        connectedCallback() {
            this.innerHTML = '<button></button>'
            this.addEventListener('click', this.onClick, true)
        }
        onClick() {
            this.innerText = 'You liked this'
        }
    })


Examples like this bug me. The React example is using a high level abstraction, the web component is directly using the API. A more accurate example would show how those React calls eventually boil down to document.createElement()

I don’t think the Web Components API was meant to be used directly all the time. You can use a framework like StencilJS:

https://stenciljs.com/

When you consider the 100KB of bulk the React example brings with it the web component example is actually pretty impressive.


The latter doesn’t have any dependencies or a build step though right?

That’s a big win that can be easily overlooked. But it depends what you’re optimising for…


I don't know many (any?) people who are greenfielding just-JavaScript, though. If you're using JavaScript in anger, you should probably be using TypeScript, and now you've got a build step regardless.

Which is not a bad thing. Incremental builds have gotten much better and much easier to deal with.


> greenfielding just-JavaScript, though

There are still some frameworks that let you write fancy front-ends without a build-step and associated tooling. And there's people like me who prefer those since they meet our needs.


You now know one person who is doing this.

:)


I use web components in a small project where I don't want a build step (it's plain HTML and I just use web components for re-usability instead of copy+pasting mark-up).


Lit framework provides a bunch of sugar that brings the dev experience closer to JSX

https://lit.dev/


Web components don't solve any of the problems day-to-day devs ACTUALLY care about.


Encapsulation and ease of reusability / configuration?

Just as an example - a widget to manage a list of number ranges. Build it up like a form element - it provides a value prop with the type ‘[number,number][]’

You need to display the current collection of min/max pairs, and add / remove / edit the collection - it’s essentially a mini CRUD component.

There is no default html element that provides this functionality - you need an ordered list of pairs of number inputs, a confirm changes button, a ‘add new’ and ‘remove this entry’ button - you need a whole collection of stuff to provide the functionality.

But once you’ve got it all worked out, wrap it in a web component and you can use it in 8 different places across your admin tool - ‘<ranges-picker />’ and boom, you’re ready to go.

Using web components to define a library of common interface elements works just fine.


Right, but react solves the problem as well with similar amount of effort, but everyone is already on react. So switching is cost with little perceived benefits


which are?


Shipping features and bug fixes without a headache


Four years ago, I found a small niche for a tiny project. Not that there were no solutions yet, but all the existing solutions lacked accessibility aspects, so I decided to develop my own, introducing the accessibility flavor [0].

The scope and limitations I had:

- Should be framework-agnostic

- Should require the minimum effort in installing

- Should not conflict with anything on the page

So, Web Component was the perfect candidate for the lib.

The framework-less approach felt overwhelming, and lit-html smelled like Polymer. So, initially, I picked up Stencil. It is still fantastic for creating component libraries, but it happened to be overkill for a tiny component. So in a few years, I rewrote my WC without Stencil, and now it feels just right.

I developed several more Web Components professionally in the past few years. I saw them as a perfect fit for the problems I had to solve. The scopes of problems were similar:

- Should be framework-agnostic

- Should require the minimum effort in installing

- Should not conflict with anything on the page

[0] https://github.com/sneas/img-comparison-slider


I have no idea what Web Components are or why I should care. Is that the problem?


One part of web components is custom HTML elements which basically means HTML elements that mean nothing until javascript execution gives them meaning. You differentiate them from the normal HTML element naming by using hyphens in the names.

When I go to a website and all I see if a bunch of blank gray boxes that means they're using web components. It encourages developers to not put the text or images in the HTML and instead to load them externally after the page is loaded with javascript. That's slow, brittle, and stupid for most web documents. It's the opposite of progressive enhancement that fails gracefully. Web components just leave blank nothing that ruins accessibility for screen readers.


When you see this, it’s a sign that someone built their web components with React/Angular/Vue on the brain.

The tech is fine. You can achieve amazing progressive enhancement with web components by understanding and effectively using <template> and <slot>. However, many web component developers never learn this.

The problem you’re describing is a training/marketing issue, as discussed in the post.


The browsers really need to make it possible to use templates and slots without javascript at all. There's no reason I shouldn't be able to define and use a custom element using HTML alone.

Until then I don't know if template and slot will take off.


This is what everyone in userland clamors for as a baseline. Decade plus old arguments about this. I have little hope this will happen


We had HTML imports and they got rid of it. No JS means No Ads.


It means less invasive tracking really, not no ads.

Ironically it was Firefox that killed it, not Google. Google was all in on HTML imports. It would have also given a non JS way to dynamically load HTML. Streaming would have been great with this.


HTML imports required JS to be used - otherwise all you had was a `link`ed DOM tree that you couldn't access. No one has really proposed a way to dynamically load anything without JS AFAIK.


Fully agreed.


FWIW, all the major screen readers fully support JavaScript. There's nothing inherently inaccessible about a website that uses JavaScript. In fact, the screen readers don't actually interpret the JavaScript at all - they just react to the page dynamically changing, it doesn't matter how it's accomplished.


There's nothing inherent no, just that javascript webpages, if some CDN doesn't load fast enough, or the dev uses some bleeding edge function not in all browsers, etc, will not end up having the text in the page. Whereas actually having the text in the page always has the text in the page.

JS sites almost always fail very badly when it fails (a relatively common event). Text sites cannot fail even when they fail.


Nope, not a problem. Because webcomponets fix a problem that nobody is having in a way that basically only benefits angular.

They are strong encapsulation around a user defined component. So if you wanted to, for example, lock down the styling on your widget, you can! (hurray?)

My cynical thoughts of why google pushes this is because it's another route to get in front of adblock. Make an ad component and now it's a lot harder for an addon to change or remove that component (and easier for the website to detect when that happens).


Even though I've never used Angular, and do not build web apps at all, we have put custom HTML elements to great use at work. It's also not about locking down styling, but it's a neat way to package up _functionality_ and behaviour that otherwise would be just defined ad-hoc in JavaScript, and to provide a dead simple way to (re)use that functionality in a declarative way from HTML.


Similar experience, it makes it easy to integrate components into partner pages because they can treat it just like any other HTML element and we don't have to worry about the vast majority of possible namespace conflicts thanks to the shadow DOM.


Right, and that is a separate tech from web components. React, for example, does not use web components yet does exactly what you are describing.

The problem web components is solving is just the strong encapsulation. Closing the escape hatches as it were.


HTML is supported natively in the browser, and custom HTML elements and their properties work at the same level as all other HTML elements as far as JavaScript, DOM, and dev tools are concerned.

React, while it may provide similar functionality to the programmer, is not natively understood, and while it can run in browsers, the 'components' in React are not handled in the same way in the browser as native HTML elements, JavaScript and its APIs don't know about them as HTML elements, DOM isn't aware of them (only their parts), and dev tools can't offer much insight because the app that's running is an unpredictable black-box to the browser.


If you default to js off, you'll spot them quickly.

I generally just head to the next site on my list/search results.


Author-defined custom HTML elements, with all of the behaviour and interactivity defined in a way the browser natively understands.


Astrojs recommends a great way to initialize & light-weight hydrate the UI using web components & custom tags.

https://docs.astro.build/en/guides/client-side-scripts/#pass...


There doesn't need to be a practical reason for something to not be popular. Popularity and merit are not the same thing.

Web components aren't popular because they haven't become popular yet. There is a lack of sufficient network effect moving in that direction.

Once they start becoming popular, the issues with them (which are relatively minor) will be resolved quickly and improvements added.

Why are most of our cities and cars still designed with fundamentals basically the same as in the 1930s? Not because there aren't better designs. It's just momentum.

High technology is like that still even though nothing physical needs to be changed.

Humans are herd animals. And the rationalization for behavior comes after the behavior not before. We subconsciously copy what other people are doing. People use React or whatever flavor-of-the-month.

Give it a couple of years and many of the flavor-of-the-month frameworks will probably be built on top of web components.


The design of cars has evolved a lot, mainly because of security regulations. Cities is another topic. They change slowly because of the required investment but in europe at least there is definitely a trend toward walkable blocks which changes a lot of urbanisation guidelines.

I agree with your first sentence: things are unpopular by default. However I disagree with your assumption: web components won't become magically popular overtime without a good reason for them to be used over all the other options. Your "flavor-of-the-month" take ignores the fact that react is now 10 years old and is boring tech. It is still one of the most widely used frontend framework.


I used Lit in a client project, which still seems to be the most common way to author web components. Styling and composition was really hard, much harder than in typical React, Vue or Svelte projects. It is future proof to build the atoms of a design system as web components (e.g. sliders, buttons and so on), but component composition is better done in a proper framework (e.g. Vue). This way companies could more easily start new projects with their existing design system, independent of the framework. I wouldn’t consider building a complex applications with Lit again, just the design system atoms.


"Web Components are slow. The last thing that’s held Web Components back is that they’re slow… slow, like brisket I like to say. Slow isn’t always a bad thing."

Yes it is, it is always a bad thing. It's not necessarily a dealbreaker, but it's never good or even neutral. I already have a mental model of the incredibly slow DOM, I don't need another dimension of performance cliffs to think about. Instead of more convoluted web standards, I want fewer, simpler and faster APIs - and may the best abstractions win.


I heard about web components a lot for years and was looking forward for an opportunity to try them. But for some reason, this is the first time I learn that it's completely reliant on JS.

My impression previously was that it brings more power back to native HTML, and lets you extends HTML elements by defining your own modular custom components instead of doing so in JS the `modern` way. Instead, turns out it's doubling down on JS to create elements, and was actually targeted towards these framework in the first place.


> My impression previously was that it brings more power back to native HTML, and lets you extends HTML elements by defining your own modular custom components instead of doing so in JS the `modern` way.

Curious what you feel defining a custom component in the browser would look like, if not with JS?


Well, my impression was that it's a revolutionary hot technology that only recent browsers support. Something like the browser doing the "SSR" natively on the fly before rendering the page DOM.

Anyway like I mentioned, I didn't get the chance to play with it and understand its purpose. Misconception -> higher expectations -> disappointment.


I read about web components a couple of years ago. What excited me was the prospect of having libraries of ready made UI elements that I could use.

Those of us who programmed in say VB6 or .NET winforms back in the day may remember that you could purchase some "ready made" grid-table editor with lots of functionality and pretty.

But I haven't seen that open source nor commercial.

It's amazing how much the current web frontend dev have to reinvent over and over again.


If I recall correctly, there was or is a web component concept or inplementation that does not originate frome those frameworks and I think I read about it on MDN or so. It is only that frontend devs push their frameworks so much, that most people associate web components with React and similar frameworks.


I've found them really useful to do microfrontends


<textarea> <input> <video> ARE all Web Components, and have been for many moons so Browser vendors could implement their own UI. So everyone using a Browser *IS* using Web Components. It just took some years for the technology to be opened up with the Custom Elements API to us mortals here in Userland.


Because react.js, vue.js, angular all had their own version of components and they're the 'mainstream' frontend frameworks.


You used to need a whole js build system because old browsers were still out there. With the death of IE11 and the death of non blink Edge, basically every current browser finally supports the full api. But this happened when functional js is the big thing and being class based the custom element api isn't as popular as it should be.


Built with lit web components, and F# no shadow root

https://funpizzashop.azurewebsites.net/

Source: https://github.com/OnurGumus/FunPizzaShop


Web components have had accessibility gaps since the beginning. I spent years working on specs and implementations to close the gap, It's insanely frustrating just how slow standardization work can be.

I'm partially relieved that web components never became that popular, because that would have made accessibility on the web worse.


If you haven't tried Stencil.js, might be worth giving it a go. I found it a dream for working with web components since it takes care of almost all the tooling, bundling, code-splitting, etc. out of the box, and has full support for Typescript, TSX and React-style props.


I think Web Components added a lot of complexity to the web platform again and we're finally hitting the breaking point where it has gotten to be too much. I don't want to deal with web components or the shadow DOM. I don't want more abstractions in the browser at this point.

On one hand, there are some use cases for this new complexity. On the other hand, it's difficult to see how the vast majority of say, React users, would ever benefit from this complexity, given that a lot of them don't seem to have problems that are solved by web components.

If we were going to focus on adding even more new browser features, I think it'd be better to focus on stuff that helps close cross-site scripting vulnerabilities, or provide better approaches towards anti-SPAM/anti-abuse than lazy, harmful remote attestation. (I mean hell, I'd love to see some very basic hashcash functionality as part of the browser; Personally, I think that would be an interesting idea for anti-SPAM, and nowhere near as catastrophically harmful as WEI.)


I would agree and would rather they had spent more effort on adding support via standard HTML5 elements for more of the standard native form widgets like comboboxes, tree controls, grid controls, etc. - all those things that already exist on almost every platform but keep having to be reinvented in javascript for no good reason.

Web Components mostly seem to be just adding complexity for the sake of avoiding simplicity.

Certainly there could be some use for custom ones, just as you can make custom native widgets. But there shouldn't be that much need that often if more of the standard functionality was available (and standard).

They did add color picker, date picker, range, and the datalist to kind of sort of emulate a combobox, so some credit for that. But unfortunately they're not consistent across browsers and may or may not be consistent with the native platform.


My experience why I use them less than I wish I did: they are really not plug and play in any website. There's plenty of css properties that pierce through the shadow dom.


> Kill Firefox before they could kill HTML Imports

I’m not sure I understand the hostility here. Isn’t the point of the web to be open standards that reach consensus?


I am currently in a process of rewriting the most monstrous frontend I've ever seen, it's written in Lit and web components. I don't recommend them to anyone, they are a relic and have failed to achieve their purpose, as now any code written in any of the popular frontend frameworks is much more interchangable than web components....




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: