Hand-written custom elements repeat the same chores: syncing attributes and properties, re-rendering, escaping values. Libraries automate them and still output standard custom elements.
Lit
Lit 310,034 (https://github.com/lit/lit 21,842 ) (Google, BSD-3-Clause, npm 2,036 install lit, version 3.3.3) adds three things to HTMLElement: reactive properties that schedule a batched update when they change, a render() method returning an html tagged template, and static styles scoped to the shadow root. Only the template parts whose values changed are updated (the template cloning technique of <template> and <slot>). The prebuilt lit-core.min.js bundle used below is about 6 KB gzipped.
<script type="module">
import { LitElement, html, css }
from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js';
class LikeButton extends LitElement {
static properties = { count: { type: Number, reflect: true }, label: {} };
static styles = css`
button { font: 16px system-ui; padding: 6px 14px; border: 1px solid #1565c0;
border-radius: 999px; background: #e3f2fd; color: #1565c0; }
:host([count="0"]) button { background: #fff; }`;
constructor() { super(); this.count = 0; this.label = 'Like'; }
render() {
return html`<button @click=${this.#like}>♥ ${this.label} · ${this.count}</button>`;
}
#like() {
this.count++;
this.dispatchEvent(new CustomEvent('liked', { detail: this.count, bubbles: true }));
}
}
customElements.define('like-button', LikeButton);
const b = document.querySelector('#b');
await b.updateComplete;
b.renderRoot.querySelector('button').click(); // two clicks for the screenshot
b.renderRoot.querySelector('button').click();
</script>
<like-button></like-button> <like-button id="b" label="Helpful"></like-button>
Because count has reflect: true, the second element now carries count="2" and the :host([count="0"]) rule no longer matches it. In TypeScript you would write @property({ type: Number }) count = 0; instead of static properties. In plain JavaScript, set defaults in the constructor as shown: a class field would shadow Lit's property accessor and break updates.
Stencil
Stencil 672,850 (https://github.com/stenciljs/core 13,132 ) (Ionic 20,753 , MIT, version 4.45.0) is a compiler, not a runtime library. You write TypeScript classes with decorators and JSX; the build emits standard custom elements, lazy-loaded bundles, type definitions and wrappers for React 7,897 , Angular 42,088 and Vue 5,482 . Ionic Framework's own components are built with it. Create a project with npm init stencil and run npm start.
import { Component, Prop, State, Event, EventEmitter, h } from '@stencil/core';
@Component({ tag: 'star-meter', styleUrl: 'star-meter.css', shadow: true })
export class StarMeter {
@Prop() max = 5; // public attribute/property: <star-meter max="10">
@State() value = 0; // internal state: changing it re-renders
@Event() rated: EventEmitter<number>; // emits a 'rated' DOM event
render() {
return Array.from({ length: this.max }, (_, i) => (
<button onClick={() => { this.value = i + 1; this.rated.emit(i + 1); }}>
{i < this.value ? '★' : '☆'}
</button>
));
}
}The compiler turns the decorators into observed attributes, accessors and a small virtual DOM renderer. Choose Stencil to publish one design system to several frameworks, and Lit for a small runtime with no mandatory build.
Shoelace and Web Awesome
Shoelace, a popular MIT-licensed set of ready-made components built with Lit, is sunset: its repository is archived at version 2.20.1 and development continues as Web Awesome 360,023 (https://github.com/shoelace-style/webawesome 1,348 ), published by the makers of Font Awesome 693 . Web Awesome 3.0 shipped in October 2025 (3.12.0 at the time of writing), uses the wa- prefix, and is themed through CSS custom properties and ::part(). Its Core components are free and MIT licensed; the paid Pro subscription adds data grids, charts, extra themes and page patterns. To try it, link https://ka-f.webawesome.com/webawesome@3.12.0/styles/themes/default.css 360,023 , load webawesome.loader.js from the same folder as a module script, and write <wa-button variant="brand">Save</wa-button>.
| Library | What it is | License | Strengths | Weaknesses |
|---|---|---|---|---|
| Lit 3.3 | Base class and templating | BSD-3-Clause | Tiny, no build needed, SSR (Labs) | You build every widget |
| Stencil 4.45 | Compiler for component libraries | MIT | Framework wrappers, lazy loading | Build step, own decorators |
| Web Awesome 3.12 | Ready-made components on Lit | MIT Core, paid Pro | Polished widgets, theming | Advanced widgets cost money |
| FAST Element 3.0 | Microsoft's base class | MIT | Powers Fluent UI Web Components | Smaller community |