HNHacker News
TopNewBestAskShowJobs

jfagnani

236 karma · joined July 1, 2025

submissionscomments
jfagnani··on Lit: a library for building fast, lightweight web components
It's not possible to make slots work without a separate tree like shadow DOM. The browser can't tell what the container for a slot is vs what content should project into it.
jfagnani··on Lit: a library for building fast, lightweight web components
It's wrong - both that it's general "current advice", and the advice itself when it does pop up.

Yes, there are some people who say to build web components without shadow DOM, but I'm convinced they're only building leaf nodes so they don't need composition with slots. As soon as they try to build any kind of container element they hit big problems.

jfagnani··on Lit: a library for building fast, lightweight web components
Slots definitely don't work without shadow DOM and there's really no way to make them work. It's the biggest problem with turning shadow DOM off.
jfagnani··on Lit: a library for building fast, lightweight web components
Lots of comments in here are about shadow DOM, so let me give my take in one place:

Yes, Lit uses shadow DOM by default (for good reasons, I think!) and yes you can turn it off component-by-component, but that does bring some challenges.

Shadow DOM is most fundamentally just a private branch of the DOM tree for a component's internal DOM details.

Frameworks have this implicitly with DOM defined in a component's template vs passed as children. But that distinction isn't visible to other code, the browser, and CSS. This is both good and bad.

The biggest thing this separate tree gives us is the ability to tell what nodes are "children" - those are the light DOM children. Once you can separate children from internals, you can build slots. Slots are holes in the internals that children get rendered into.

Without something like shadow DOM you can't have slots. And without slots you don't have interoperable composition and you don't have viable container elements. You need some way to place children in an element that isn't confused with the element's own DOM.

So to me, before encapsulation and style scoping, interoperable composition is the most important feature, and that's really why Lit defaults to shadow DOM on. Without it we'd need some special `.children` property and Lit's component composition suddenly wouldn't be compatible with everyone else.

But the style encapsulation is often a major pain for developers, especially if they're trying to integrate web components into an existing system with whole-page stylesheets. It's a big blocker to a lot of design systems porting to web components.

That's one reason I proposed something called "Open Styleable Shadow Roots"[1] which would let styles from outer scopes cascade into a shadow root - a way to break open style encapsulation but keep slots. It's been hard convincing browser vendors that this is needed, but I'm holding out hope that it makes progress soon.

[1]: https://github.com/WICG/webcomponents/issues/909

jfagnani··on Lit: a library for building fast, lightweight web components
The great thing about web components is that you can build them however works best for you.

Native web component APIs don't have the DX that many people expect though, because they are so low-level. Lit provides just that declarative reactivity on top.

jfagnani··on Lit: a library for building fast, lightweight web components
Thanks!

Elements are kept stable as long as the template containing them is rendered.

The template docs try to get this across by saying that Lit "re-render only the parts of template that have changed." Maybe that needs more detail.

There are details here: https://github.com/lit/lit/blob/main/dev-docs/design/how-lit...

jfagnani··on Lit: a library for building fast, lightweight web components
Yes!
jfagnani··on Lit: a library for building fast, lightweight web components
Import assertions were replaced with import attributes (`assert` replaced by `with`).

See https://caniuse.com/mdn-javascript_statements_import_import_...

jfagnani··on Lit: a library for building fast, lightweight web components
There really is no way to metaprogram against class fields except with decorators.

Class fields aren't on the prototype. They're essentially Object.defineProperty() calls that occur right after the super() call of a constructor. You can't get at them at all from the class declaration.

I know more than one way to do something is sometimes a downside, but we have a large audience an a huge portion of it wants to use TypeScript and declarators, and another huge portion of it wants to use vanilla JavaScript. We factored things to give the best DX we could to each group with relatively little cost to us.

As for why metaprogramming, we want a component to update when it's state is changed. Like plain web components, Lit uses classes.

So here, we want setting `el.name` to cause the component to re-render:

    class MyElement extends LitElement {

      name = 'World';

      render() {
        return html`<h1>Hello ${this.name}</h1>`
      }
    }
We do that by replacing `name` with a getter/setter pair where the setter schedules an update of the instance. Without decorators, there's no way to see that `name` even exists, much less replace it.

Decorators actually do the replacement for us, we just have to build the thing to replace it with:

    class MyElement extends LitElement {

      @property() accessor name = 'World';

      render() {
        return html`<h1>Hello ${this.name}</h1>`
      }
    }
jfagnani··on Lit: a library for building fast, lightweight web components
Lit has always been designed partially as a prototype for where web component standards could go in the future. That's a big reason Lit is fairly conservative and un-opinionated. It doesn't try to undo or paper-over any of the DOM APIs, but add to them instead.

There is a proposal in TC39 for native signals, which I think would make a huge dent towards library-less reactivity.

I'm also working on a proposal for native reactive templating which would more-or-less obsolete lit-html. I wrote about the idea some on my blog:

- The time is right for a DOM templating API https://justinfagnani.com/2025/06/26/the-time-is-right-for-a...

- What should a native DOM templating API look like? https://justinfagnani.com/2025/06/30/what-should-a-dom-templ...

jfagnani··on Lit: a library for building fast, lightweight web components
Lit's just a JavaScript library published as standard modules, so it doesn't require a bundler. Using Lit efficiently is the same as using any other library.

HTTP/3, import maps, and HTML preloads can make unbundled app deployment almost reasonable in some cases. You'd still miss out on minification and better compression form a bundle. Shared compression dictionaries will make this better though.

jfagnani··on Lit: a library for building fast, lightweight web components
I think web components already compete extremely well for application development, and you see very complex apps built with Lit out there: Photoshop, Firefox, Chrome OS, Chrome DevTools.

Apps are well served because they have more control about how components are used: they can import the same shared styles into every component, take are to not double-register elements, etc.

But I think there are some important standards still missing that would open things up even more in the design system and standalone components side:

- Scoped custom element registries. This moves away from a single global namespace of tag names. Seems like it's about to ship in Safari. Chrome next.

- Open styleable shadow roots. Would allow page styles to flow into shadow roots. This would make building components for use with existing stylesheets easier.

- CSS Modules. Import CSS into JS. Shipping in Chrome. About to land in Firefox.

- ARIA reference target: make idref-based reference work across shadow roots

jfagnani··on Lit: a library for building fast, lightweight web components
Lit maintainer here. I should be going to bed, but I'll answer any questions if people have any!

Not sure why Lit showed up on the front page tonight :)

jfagnani··on Lit: a library for building fast, lightweight web components
You have a very large axe to grind against web components and Lit, and you show up just about everywhere to make the same comments, but I'll play along anyway:

Yes, Lit templates give some special meaning to attribute names with a few prefixes. No, it's not "HTML-like". It's valid HTML. Not that it matters much. You bring this up all the time but I'm not sure what the actual criticism is. Developers seem to understand the small syntax carve-out just fine.

No, there are no custom JavaScript rules. Templates have some rules. I'm not sure why they wouldn't? In general you can't make things like tag and attribute names dynamic because you can't change them in HTML. You can actually write the template you show with what we call static templates though.

`classMap()` is a template directive. It has some rules about how it's used in templates, just like other JS functions can have rules about how their used. I'm not sure what makes that not a function.

But to your main point: Lit is not like React because it's not a framework. Lit helps you make custom elements - it's an implementation detail of some web components. Everything else about those elements: how you instantiate them, style them, where they work, etc., is all defined by the HTML and DOM standards. React is a framework, and defines its own rules about how its components work.

jfagnani··on Lit: a library for building fast, lightweight web components
Decorators are the only way to metaprogram over class fields in JS. Otherwise they're not even detectable on the prototype.

We use them to make fields reactive mostly, and I love how declarative they are. But we use them sparingly. I personally don't love how some libraries try to put a lot of things into decorators that could have been standard class features, like a static field or a method.

edit: As mentioned by skrebbel, decorators are optional. Every decorator has a simple plain-JS way of doing it. Event reactive properties: https://lit.dev/docs/components/properties/#declaring-proper...

We also put a lot of effort into making all of our documentation and playground samples on lit.dev available in both JavaScript and TypeScript with decorators. There's a switch that will change everything on the site from JS to TS globally.

jfagnani··on <template>: The Content Template element
Plates style isn't what developers expect from modern templating syntaxes. Every popular syntax today supports inline expressions.
jfagnani··on <template>: The Content Template element
My understanding is that in implementations any unknown type creates a "data block", which is just unprocessed text.'

I wouldn't use application/json just in case browsers start supporting that and it has different semantics than whatever custom thing you might do, causing a webcompat issue when the native feature rolls out.

Although with JSON, it's pretty unlikely that there would be any differing semantics. JSON modules in JS are just JSON blocks with no special additions and no named exports. That's what inline versions would be as well.

jfagnani··on <template>: The Content Template element
I wrote a couple of blog posts on why we should add native templating to the DOM so we'd need fewer libraries.

The time is right for a DOM templating API: https://justinfagnani.com/2025/06/26/the-time-is-right-for-a...

What should a native DOM templating API look like?: https://justinfagnani.com/2025/06/30/what-should-a-dom-templ...

jfagnani··on <template>: The Content Template element
Author of lit-html here.

Yeah, Lit's tagged template literals and render() method are basically a shorthand for making a <template> element, marking the spots where expressions go, cloning the template, then filling in those sports.

jfagnani··on <template>: The Content Template element
You should use document.importNode() to clone templates.

Template contents are in a separate document from the main document, which is what makes them inert. importNode() adopts the nodes into the document so they have the correct prototype immediately (which includes custom elements). Otherwise the adopt steps are run when the elements are first attached to the document, which adds another tree walk to the insert/append operation that costs some time.

So:

    document.importNode(elem.content, true);
Then you'll have a DocumentFragment you can pull nodes out of. Or just append the whole fragment.
jfagnani··on <template>: The Content Template element
No. I would use <script type="-json">

<script> parses its contents as text, whereas <template> parses as DOM. This means you don't have to escape `<`, just `</script>`.

Myself and some browser engineers been working on proposals to allow for inline modules, including JSON, that are importable into other modules via regular import statements.

This is why I recommend the "-json" type - so it doesn't collide with a future native "json" type.

jfagnani··on <template>: The Content Template element
There is a spec issue open for HTML Modules [1] along with a few proposals. The concept needs some motivated champions to see it through. Microsoft has shown some interested in this lately.

There are userland single-file component projects, like my Heximal[2] project. Though Hexiaml specifically doesn't tackle the module format - it requires some kind of HTML Module polyfill.

I don't think the current situation is so bad though. The container format should mostly be irrelevant to consumers of a component. You need to import the component defintion whether that definition is in a .js file or .html file. Once you import it you use it the same.

There should be very few cases where the file format matters. Maybe for use when JS is turned off if HTML-based web components are considered safe to evaluate (their templates might expressive enough to potentially allow attacks).

[1]: https://github.com/WICG/webcomponents/issues/645

[2]: https://heximal.dev/ (Sorry the site is messed up on mobile. I need to fix it!)

jfagnani··on Events
I think events are a bit unsung and underutilized in a lot of web projects. Events are really powerful and you can build systems with them that can replace proprietary framework features with interoperable protocols.

Context: Components that need a context value can fire an event to request it. Any other element or listener higher in the tree can handle the event and provide a value via the event object. Event are synchronous, so you can get values synchronously. The Web Components Community Group maintains an interoperable context community protocol: https://github.com/webcomponents-cg/community-protocols/blob...

Suspense: Components that have some pending some work, like awaiting data to render, can fire an event to signal that they have pending work. The event can carry a promise, and then a suspense-boundary-like component can handle the event and display a spinner until all the pending work in the tree below it is finished. Another protocol: https://github.com/webcomponents-cg/community-protocols/blob...

Error boundaries: A component can fire an ErrorEvent if it fails to render, and an error boundary component can display the error or some other user affordance.

Child-parent registration: A child component can fire an event to tell some parent that it's available. This is useful for using components as plugins. A <code-mirror> element could have children that provide language support, syntax highlight themes, etc.

Actions: Redux-like actions can be done with events instead. You can build a nice data-down, events-up system this way with very little code and very loose coupling.

Event buses: components can listen for events on a top-level node like document, and they'll receive every event of that type from every other dispatcher.

jfagnani··on What should a native DOM templating API look like?
Very uncanny! I like it :)

I even have a lot of those things planned, just not enough time!

I didn't do anything that required client-side JS yet, but the first things on that list are out-of-order rendering; watch mode (page reload); and hot module replacement.

jfagnani··on What should a native DOM templating API look like?
I've definitely explored how those frameworks work, and I've written several signals integrations for Lit.

My claim, having done that, is that template re-rendering and fine-grained reactivity are entirely compatible, and neither takes away from the other.

Template re-rendering is incredibly cheap, and not at all like React/vdom. It does a few simple checks: 1) that the new template is the same as the last, 2) it runs through the linear list of values and compares them to the last, writing them if they changed. This isn't like VDOM which has to traverse two fine-grained vdom trees comparing for differences. Many huge web applications like Photoshop, Reddit, and parts of YouTube use template re-rendering.

And signals work great with this. If your template is "signal pure" then it never needs to re-render. If it's partially signals and partially non-signal data, then it only needs to re-render when non-signal data changes.

Signals are great, but signal-only systems require that all data be wrapped in them. And I have seen large apps have problems with the memory overhead of the automatic dependency tracking and change detection at each composed signal in the graph. Memory pressure and GC could be problematic for the TC39 signals proposal (though hopefully not).

To answer your next point about who this is for: lots of different audiences.

First is plain web devs. I've seen in multiple large dev orgs moves to standardize all DOM creation outside of frameworks on lit-html or something similar, mainly out of security concerns. But templated DOM creation is so common, that it's really a big whole in the web platform that it's not built in. The only reason it's not is that platform maintainers are afraid of stepping on the toes of frameworks. I don't think that's a good reason.

Second is frameworks. Whether or not the exact set of current frameworks migrate to this, a framework created for a platform with templating built-in would have a high probably of just using it instead of rolling their own. You can easily imagine a React-alike built on top of this API, as I've seen people do with lit-html. That reduces code size and increases perform for libraries and users.

Third is web components authors. Many reach for something like Lit main for the templating. I don't think they should have to, even as the maintainer of Lit. Vanilla web components + native templating takes care of a large amount of component concerns for those developers.

Fourth is the platform itself. Part of my reason for working on this proposal now as opposed to my usual "I'll get to that one day" tasks is because I've seen so many people ask for an HTML-based templating solution, ie a better `<template>`. I support that, but I don't think we can easily get there all at once. This is an attempt to build the story and capabilities for the subset of features that an JS API needs first so that the infrastructure can be used for HTML templates. If the JS API gets in, then the HTML API can reuse it, adding the expressions, scopes, control flow, etc., that HTML needs and JS doesn't.

jfagnani··on What should a native DOM templating API look like?
Interesting server framework! It looks very similar in some ways to a server I started last year out of frustration with Koa and Express called Zipadee: https://github.com/justinfagnani/zipadee?tab=readme-ov-file#...

The templates look basically identical. html-tagged template literals that support streaming and async values and automatically escape values.

What Koa does will allowing strings by be returned with a default HTML mimetype is security malpractice, IMO. It's way to easy to just interpolate user-controlled values.

jfagnani··on What should a native DOM templating API look like?
This API is definitely secure by default, and that's one of the constraints and requirements I mention in the post.

The API is secure because it separates static developer controlled strings from dynamic and possibly user-controlled values by JavaScript syntax. Values from text bindings are written to the DOM by setting TextNode.data, which escapes the value first.

jfagnani··on What should a native DOM templating API look like?
Author here. I could include Svelte, but honestly it's still like the others: markup with embedded binding expressions and control flow. It would only bolster my claim that popular template syntaxes are similar.
jfagnani··on What should a native DOM templating API look like?
Author here. Property and event disambiguation syntax is definitely far on the easy to change side of things.

I personally think the sigils are easier to read and write than perfixes, and these particular sigils have popped up independently in multiple libraries like Vue, Lit, and LighterHTML.

But this API could use prefixes, or like Vue, it could support both.

jfagnani··on What should a native DOM templating API look like?
Author here: Those don't look like HTML enough because they're not markup. Every single one of the top frameworks uses markup with interpolations as their basic template format: React, Vue, Angular, Preact, Lit, Svelte, Solid, Quik, Stencil, Marko, Polymer, FAST, and on and on.

Frameworks and rendering libraries that don't have a markup-based template syntax are just very rare.

And I think for understandable reasons: Markup-based templates look like the output, and web developers know HTML, so the templates are easier to read and write.

That StackOverflow question seems unrelated because it's asking about untagged template literals. With tagged template literals, depending on the return type, you can absolutely get to the underlying values.