Skip to content

Why Lit

In a vanilla Custom Element, every time state changes you must manually update the DOM. Here is a counter built without Lit:

class VanillaCounter extends HTMLElement {
constructor() {
super();
this._count = 0;
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this._render();
}
_render() {
this.shadowRoot.innerHTML =
'<p>Count: <b>' + this._count + '</b></p>' +
'<button id="inc">+</button>' +
'<button id="dec">-</button>';
this.shadowRoot.querySelector('#inc').addEventListener('click', () => {
this._count++;
this._render();
});
this.shadowRoot.querySelector('#dec').addEventListener('click', () => {
this._count--;
this._render();
});
}
}
customElements.define('vanilla-counter', VanillaCounter);

This approach has several pain points:

  • innerHTML re-creates the whole subtree on every update — event listeners are re-added each time.
  • No declarative binding — every update requires imperative DOM surgery.
  • No property observation — you must wire attributeChangedCallback manually.

Lit solves these pain points with three features:

  • static properties — declares reactive properties; changing one schedules an efficient re-render.
  • html`...` tagged template — a declarative template that diffs and patches only the changed parts of the DOM.
  • static styles with css`...` — styles are adopted once into the Shadow DOM stylesheet, not re-parsed on each render.

Lit is not always necessary. Use plain HTMLElement when:

  • The element is purely structural (a layout wrapper with no state).
  • You are writing a one-off widget with no dynamic updates.
  • Bundle size is the primary constraint and the element does almost nothing.

Use Lit when state changes need to drive UI updates, when you have multiple reactive properties, or when you are building a component library.

The Lit counter — same behaviour, far less code

Section titled “The Lit counter — same behaviour, far less code”
What is a key problem with using innerHTML for re-renders in vanilla Custom Elements?
Which Lit feature replaces manual DOM updates with efficient diffing?
When is it appropriate to skip Lit and use a plain HTMLElement?