← Blog
Dev Web Components Lit Frontend JavaScript

Web Components: Why Lit is the Right Framework

Web Components have existed for over ten years. They are a browser standard, natively supported by all modern engines, without external dependencies. Yet almost no one uses them.

The reason isn’t technical. It’s that writing them by hand is so verbose it’s discouraging. Then I discovered Lit, and I changed my mind.

The problem with native Web Components

A native Web Component is a JavaScript class that extends HTMLElement. The API is powerful but ceremonial:

class MyButton extends HTMLElement {
  static observedAttributes = ["label"];

  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  connectedCallback() {
    this.render();
  }

  attributeChangedCallback() {
    this.render();
  }

  render() {
    this.shadowRoot.innerHTML = `
      <button>${this.getAttribute("label")}</button>
    `;
  }
}

customElements.define("my-button", MyButton);

This is a button. With one prop. No real reactivity, no template system, no state management — everything has to be implemented by hand every time.

Nobody writes like this in production. And that’s why Web Components haven’t taken off.

What is Lit

Lit is a Google library (open source, ~6KB gzipped) that adds exactly what native Web Components are missing: a reactive template system, property management, and clear lifecycle hooks.

It’s not an alternative framework — it compiles to standard Web Components. What it produces is a custom element that can be registered with customElements.define, usable in any HTML, with any framework or without one.

The same button from before, in Lit:

import { LitElement, html, css } from "lit";
import { customElement, property } from "lit/decorators.js";

@customElement("my-button")
class MyButton extends LitElement {
  @property() label = "";

  static styles = css`
    button { padding: 0.5rem 1rem; }
  `;

  render() {
    return html`<button>${this.label}</button>`;
  }
}

When label changes, Lit updates only the part of the DOM that depends on that property — no full re-render, no virtual DOM.

Why Lit is the right choice

1. Zero runtime dependencies

React needs React. Vue needs Vue. A compiled Lit component is a standard Web Component — the browser runs it without any library to load. The bundle is your application’s, not your application’s plus a framework’s.

For distributed design systems, widgets to embed in third-party sites, components that must run anywhere: it’s a structural advantage.

2. Works with any stack

A Lit component is used like any other HTML tag:

<!-- In a static HTML file -->
<my-button label="Submit"></my-button>

<!-- In React -->
<MyButton label="Submit" />

<!-- In Astro -->
<my-button label="Submit" />

<!-- In Vue -->
<my-button label="Submit" />

This is the real advantage of Web Components — and Lit makes it practical. You can build a design system once and use it in any project, regardless of the framework that changes every two years.

3. Shadow DOM: Encapsulated styles for real

Shadow DOM isn’t just an implementation detail — it’s real isolation. Styles defined inside a Lit component don’t leak out, and global styles don’t leak in (unless you let them through with CSS custom properties).

static styles = css`
  /* These styles only exist inside this component */
  :host {
    display: block;
  }
  button {
    background: var(--button-bg, #0070f3);
    color: white;
  }
`;

No class conflicts, no BEM, no CSS Modules. Encapsulation is guaranteed by the platform.

4. Granular reactivity without magic

Lit tracks which parts of the template use which property. When a property changes, it updates only that portion of the DOM — it doesn’t re-render everything.

@customElement("user-card")
class UserCard extends LitElement {
  @property() name = "";
  @property() online = false;

  render() {
    return html`
      <div class="card">
        <span class="name">${this.name}</span>
        <span class="status">${this.online ? "online" : "offline"}</span>
      </div>
    `;
  }
}

If only online changes, Lit updates only the span.status. Simple, predictable, without virtual DOM overhead.

When to use Lit

Lit isn’t the answer to everything. For complex SPA applications with routing, global state management, and server-side rendering, React or SvelteKit remain more mature choices.

Where Lit excels:

  • Design systems shared across multiple stacks or teams
  • Widgets to embed in heterogeneous contexts (CMS, third-party sites)
  • Standalone components that must last over time without being tied to a specific framework
  • Progressive enhancement on sites with existing HTML

Conclusion

Web Components didn’t take off because the native DX is poor. Lit solves exactly that problem — it adds reactivity, template expressions, CSS scoping, and TypeScript support while keeping the output 100% standard.

It’s not a compromise. It’s Web Components as they should have been from the beginning.