ACL Digital

Home / Blogs / The Ultimate Guide to Lit: Build Once, Use Across Angular, React, and Vue Frameworks
The Ultimate Guide to Lit
April 9, 2026

5 Minutes read

The Ultimate Guide to Lit: Build Once, Use Across Angular, React, and Vue Frameworks

The Component Paradox: Breaking Free from Framework Lock-In

In today’s fragmented front-end landscape, most development teams face a dilemma: how do you maintain a single, consistent design system when different teams use different frameworks—React for the main application, Angular for legacy tools, and Vue for a marketing site?

The traditional answer often involves rewriting components in three different codebases, leading to inevitable inconsistencies, massive maintenance overhead, and wasted time. Imagine maintaining a simple button component across three frameworks—every bug fix, every style update, every accessibility improvement must be implemented three times. The cognitive load alone is staggering, not to mention the risk of divergence where the “same” component behaves differently across applications.

Fortunately, there is a better way. Lit directly addressed the challenges of cross-framework consistency. It is a lightweight library for building components that works across frameworks without rewrites, using native web standards.

In this comprehensive blog, we’ll explore what Lit is, why it’s gaining traction in enterprise environments, and most importantly, how to seamlessly integrate a Lit component into Angular, React, and Vue applications. We’ll also dive into advanced patterns, performance considerations, and real-world implementation strategies.

What is Lit? The 5KB Powerhouse

Lit is a simple, modern library developed by Google that serves one primary purpose: making it easy to build and share Web Components. Born from the learnings of the Polymer project, Lit represents a refined, minimalist approach to component development that embraces modern JavaScript and web standards.

It is not a monolithic framework; it’s a lightweight collection of tools built on top of the browser’s native capabilities:

  • lit-html: A super-efficient templating library that uses tagged template literals to render HTML. It only updates the small, dynamic parts of the DOM that have changed, ensuring blazing-fast performance. Unlike virtual DOM implementations that compare entire tree structures, lit-html uses a clever technique of creating static template parts and only diffing the dynamic expressions.
  • lit-element: A base class that provides reactive property management and lifecycle methods, making your components automatically re-render when data changes. It handles the complexity of the Custom Elements lifecycle while providing a developer-friendly API.

The Technical Foundation: Web Components Standard

To truly appreciate Lit, you need to understand the Web Components standard it is built upon. Web Components consist of three main technologies:

  • Custom Elements: Define your own HTML elements with custom behavior
  • Shadow DOM: Encapsulate markup and styles, preventing style leakage
  • HTML Templates: Declare fragments of markup that can be cloned and inserted

Lit acts as a thin abstraction layer over these standards, smoothing out cross-browser inconsistencies and providing modern conveniences like decorators and reactive properties.

Why Choose Lit Over a Framework?

The main argument for Lit is its ability to create components that are truly reusable across all major frameworks, directly supporting consistent design and reducing duplication.

FeatureBenefit
Native StandardsWorks in any project that runs in a modern web browser.
Tiny SizeWeighs around 5KB (minified and gzipped), dramatically reducing bundle size.
Encapsulation (Shadow DOM)Styles are automatically scoped, preventing the host app’s CSS from breaking your component.
PerformanceUses efficient DOM diffing to achieve updates faster than many virtual DOM implementations.
Long Term StabilityBuilt on web standards that will be supported for decades, not framework versions that change yearly.
Zero Lock InComponents work anywhere HTML works, including server-side rendering contexts.

The AI Factor: Why Not Just "Migrate" with Prompting?

Today, advancements in AI can translate a React component into an Angular component. This might raise a question: are framework-agnostic libraries needed? The answer focuses on ownership versus duplication:

  • Single Source of Truth: AI-generated migrations create multiple decoupled codebases to manage; Lit allows you to maintain one single component that works everywhere.
  • The “Translation Tax”: Every time AI migrates a component to a new framework, it inherits that framework’s specific runtime overhead, whereas Lit stays a “5KB Powerhouse” regardless of the host.
  • Maintenance at Scale: If the primary “brand color” in your design system changes, consider whether you want to prompt an AI to update several framework repositories or update a single Lit element and see the changes propagate everywhere instantly.
  • Native Longevity: AI translates for the current version of a framework; Lit targets the Web Component standard, ensuring your code remains functional even as frameworks evolve or become obsolete.

Real-World Scenarios Where Lit Shines

The AI UX Practice Model visual selection infographic

Scenario 1: The Acquisition Challenge
Your company acquires another company that uses a different framework. Instead of rewriting their entire UI or maintaining parallel design systems, you can gradually introduce Lit components that work in both codebases.

Scenario 2: Gradual Migration
You’re migrating from AngularJS to React, but it’s a multi-year process. Lit components can exist in both old and new code, allowing you to standardize UI elements without blocking the migration.

Scenario 3: Micro-Frontend Architecture
Different teams own different parts of your application, each using their preferred framework. Lit provides the shared UI vocabulary that enables a consistent user experience across all teams.

Scenario 4: Third-Party Widgets
You’re building embeddable widgets for external clients. You cannot control what framework they use, so you need components that work everywhere with minimal integration friction.

The Lit Component: Anatomy of a Standard Element

Building a Lit component is straightforward and highly readable. Let’s examine a complete example with detailed annotations:


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

@customElement('my-lit-button')
export class MyLitButton extends LitElement {
  // 1. SCOPED STYLES
  static styles = css`
    :host {
      display: inline-block;
      padding: 10px 20px;
      border: 1px solid blue;
      border-radius: 5px;
      cursor: pointer;
      transition: all 0.2s ease;
    }
    
    :host(:hover) {
      background-color: rgba(0, 0, 255, 0.1);
    }
    
    :host([disabled]) {
      opacity: 0.5;
      cursor: not-allowed;
    }
    
    button {
      background: none;
      border: none;
      color: inherit;
      font: inherit;
      cursor: inherit;
      padding: 0;
    }
  `;

  // 2. REACTIVE PROPERTIES
  @property({ type: String })
  label = 'Click Me';
  
  @property({ type: Boolean, reflect: true })
  disabled = false;
  
  @property({ type: String })
  variant = 'primary';

// 3. TEMPLATE
render() {
  return html`
    <button 
      @click=$ {this._handleClick}
      ?disabled=$ {this.disabled}
      class=$ {this.variant}
    >
      $ {this.label}
    </button>
  `;
}

  // 4. EVENT EMISSION
  _handleClick() {
    if (!this.disabled) {
      this.dispatchEvent(new CustomEvent('button-clicked', {
        detail: { 
          timestamp: new Date(),
          label: this.label 
        },
        bubbles: true,
        composed: true
      }));
    }
  }
}

Understanding Lit's Reactive System

The @property decorator does more than just declare a property—it sets up a reactive chain:

  • When you set label = “New Text”, Lit detects the change
  • It schedules a render update (batched for performance)
  • Only the affected DOM nodes are updated
  • The change completes in microseconds

The reflect: true option mirrors the property value to an HTML attribute, which is crucial for CSS selectors and accessibility tools.

Shadow DOM: Your Style Safety Net

The Shadow DOM creates a boundary around your component’s internal markup. Styles defined inside cannot leak out, and external styles (mostly) cannot leak in. This is powerful for:

  • Preventing conflicts: Your .button class won’t clash with the host app’s .button
  • Ensuring consistency: Component looks the same regardless of page context
  • Simplifying maintenance: You can refactor the internal structure without breaking consumers

However, CSS custom properties (variables) do penetrate the Shadow DOM, which provides a controlled theming mechanism:

javascript
static styles = css`
:host {
background: var(--button-bg, blue);
color: var(--button-text, white);
}
`;

This is where the magic happens. Since our component (<my-lit-button>) is a Custom Element, it integrates cleanly into any application.

1. Integration in React

React uses its own synthetic event system, which historically makes handling events from Web Components tricky.

Task

Solution

Using the Element

Render it like any HTML element: <my-lit-button label=”Hello”/>

Handling Events

Events like button-clicked are not automatically captured by onClick. You must use a ref and useEffect to manually attach the listener.

Simplifying React

BEST PRACTICE: Use the @lit/react package. It generates a wrapper component that handles all property and event translation for you, making the Lit component feel native in React.

jsx

import { useEffect, useRef } from 'react';

function App() {
const buttonRef = useRef(null);
useEffect(() => {
const button = buttonRef.current;
const handler = (e) => {
console.log('Button clicked:', e.detail);
};
button.addEventListener('button-clicked', handler);
return () => button.removeEventListener('button-clicked', handler);
}, []);
return <my-lit-button ref={buttonRef} label="React Button" />;
}

Using @lit/react for Seamless Integration


jsx

import { createComponent } from '@lit/react';
import { MyLitButton } from './my-lit-button';
export const MyLitButtonReact = createComponent({
react: React,
tagName: 'my-lit-button',
elementClass: MyLitButton,
events: {
onButtonClicked: 'button-clicked'
}
});
// Now use it like a native React component
function App() {
return (
<MyLitButtonReact
label="React Button"
onButtonClicked={(e) => console.log(e.detail)}
/>
);
}

2. Integration in Angular

Angular’s compiler usually throws an error when it encounters an unknown tag. A simple config fix is all you need.

Task

Solution

Using the Element

Render it in your template: <my-lit-button [label]=”dataProp” (button-clicked)=”handleEvent($event)”></my-lit-button>

Enabling Use

In the module where you use the component, add the CUSTOM_ELEMENTS_SCHEMA to the @NgModule (or component schema). This tells Angular to treat the tag as a custom element.

typescript

// app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

// Import the Lit component definition
import '../components/my-lit-button';

@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
bootstrap: [AppComponent]
})

export class AppModule { }
typescript

// app.component.ts

import { Component } from '@angular/core';

@Component({

selector: 'app-root',

template: `

<my-lit-button

[label]="buttonLabel"

(button-clicked)="handleClick($event)">

</my-lit-button>

`

})

export class AppComponent {

buttonLabel = 'Angular Button';

  handleClick(event: CustomEvent) {

console.log('Clicked at:', event.detail.timestamp);

}

}

3. Integration in Vue

Vue’s binding system is very intuitive for Custom Elements, but it needs to be told not to treat the component tag as a native Vue component.

Task

Solution

Using the Element

Render it in your template: <my-lit-button :label=”dataProp” @button-clicked=”handleEvent”></my-lit-button>

Enabling Use

Configure your build tool (e.g., in vite.config.js or vue.config.js) to include the component prefix in the compilerOptions.isCustomElement array. This prevents Vue from trying to resolve it as a framework component.

javascript

// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith('my-lit-')
}
}
})
]
});

javascript
<!-- App.vue -->

<script setup>
import { ref } from 'vue';
import '../components/my-lit-button.js';

const buttonLabel = ref('Vue Button');

const handleClick = (event) => {
  console.log('Clicked:', event.detail);
};
</script>

<template>
  <my-lit-button 
    :label="buttonLabel"
    @button-clicked="handleClick">
  </my-lit-button>
</template>

Key Lit Capabilities

1. Property Validation and Conversion

Lit automatically converts attribute strings to the appropriate property types:

javascript

@property({ type: Number })
count = 0;

@property({ type: Boolean })
active = false;

@property({ type: Array })
items = [];

@property({
type: Object,
hasChanged: (newVal, oldVal) => {
return JSON.stringify(newVal) !== JSON.stringify(oldVal);
}
})
config = {};

2. Lifecycle Methods

Lit provides several lifecycle hooks for advanced scenarios:

javascript
connectedCallback() {
super.connectedCallback();
// Component added to DOM
this._startPolling();
}

disconnectedCallback() {
super.disconnectedCallback();
// Component removed from DOM
this._stopPolling();
}

updated(changedProperties) {
super.updated(changedProperties);
if (changedProperties.has('data')) {
// React to specific property changes
this._processData();
}
}

firstUpdated() {
// Called after first render
this._focusInput();
}

3. Slots for Composition


javascript
render() {
  return html`
    <div class="card">
      <header>
        <slot name="header">Default Header</slot>
      </header>
      <main>
        <slot></slot>
      </main>
      <footer>
        <slot name="footer"></slot>
      </footer>
    </div>
  `;
}

Usage:


javascript
<my-card>
  <h2 slot="header">Custom Title</h2>
  <p>Main content goes here</p>
  <button slot="footer">Action</button>
</my-card>

4. State Management Patterns


javascript
import { ReactiveController } from 'lit';

class FetchController implements ReactiveController {
  host;
  value = null;

  constructor(host, url) {
    this.host = host;
    this.url = url;
    host.addController(this);
  }

  async hostConnected() {
    const response = await fetch(this.url);
    this.value = await response.json();
    this.host.requestUpdate();
  }
}

// Use in component
class MyComponent extends LitElement {
  data = new FetchController(this, '/api/data');

  render() {
    return html`<div>\${this.data.value?.title}</div>`;
  }
}

Performance Optimization Strategies

1. Conditional Rendering

Use JavaScript expressions for efficient conditional rendering:

javascript

render() {
  return html`
    ${this.loading
      ? html``
      : html``
    }
  `;
}

2. List Rendering with Keys

For dynamic lists, use the repeat directive:

javascript
import { repeat } from 'lit/directives/repeat.js';

render() {
return html`
<ul>
${repeat(
this.items,
(item) => item.id,
(item) => html`<li>${item.name}</li>`
)}
</ul>
`;
}

3. Lazy Loading

Split large components with dynamic imports:

javascript
async loadEditor() {
const { CodeEditor } = await import('./code-editor.js');
return new CodeEditor();
}

The Future is Standards-Based

Lit distinguishes itself by enabling interoperability across Angular, React, or Vue. It uses the Web Component standard to allow development teams build a single source of UI components, separate from framework dependencies, ensuring consistency and future-proofing.

Looking Ahead: Server-Side Rendering

Lit is actively developing SSR capabilities through @lit-labs/ssr, enabling components to render on the server for improved performance and SEO.

Declarative Shadow DOM

Modern browsers support Declarative Shadow DOM, allowing server-rendered components to hydrate without JavaScript execution delays.

The Broader Ecosystem

Lit integrates with modern tools seamlessly:

  • Storybook: Document and test components in isolation
  • TypeScript: Full type safety with decorators
  • Build Tools: Works with Vite, Webpack, Rollup, and esbuild
  • Testing: Compatible with Web Test Runner, Jest, and Playwright

Conclusion: When Should You Choose Lit?

Lit is the right choice when:

  • You need components that work across multiple frameworks.
  • You’re building a design system for diverse teams.
  • Bundle size is a critical concern.
  • You want components that will work for years without migration.
  • You value web standards over framework abstractions.

Lit might not be the right choice when:

  • You’re building a single-framework application with no sharing needs.
  • Your team is deeply invested in framework-specific patterns (like JSX with React hooks)
  • You need IE11 support (though polyfills exist)

If you are building a shared component library, a design system, or just want to future-proof your UI blocks, Lit offers an efficient, tiny, and powerful solution that respects the web platform while providing a modern developer experience.

The future of web development isn’t about choosing the “right” framework—it’s about building interoperable components that transcend framework boundaries. Lit is the bridge that makes that future possible today.

Supported Frameworks

Here’s a comprehensive list of frameworks and environments where you can use Lit components:

JavaScript Frameworks

  • React – Using @lit/react wrapper or manual integration
  • Angular – With CUSTOM_ELEMENTS_SCHEMA
  • Vue 2 & Vue 3 – With isCustomElement configuration
  • Svelte – Native Custom Elements support
  • js – Works with Custom Elements out of the box

Meta-Frameworks

  • js (React-based) – SSR and client-side
  • Nuxt (Vue-based) – With proper configuration
  • SvelteKit – Full support
  • Remix (React-based) – Works with React integration
  • Astro – Excellent Web Components support
  • Qwik – Native Custom Elements support
  • Fresh (Deno) – Supports Web Components

Other Environments

  • Vanilla HTML/JavaScript – Native usage, no framework needed
  • WordPress – Can be embedded in themes/plugins
  • Shopify – For custom storefronts
  • Electron – Desktop apps
  • Capacitor/Ionic – Mobile apps

CMS & Site Builders

  • Drupal – Custom Elements integration
  • Joomla – Can embed Web Components
  • Webflow – Via custom code embeds

The key advantage: Lit components work anywhere that supports standard HTML, making them truly universal across virtually any web development context!

References & Further Reading

Getting Started

Framework Integration

Real-World Examples

Turn Disruption into Opportunity. Catalyze Your Potential and Drive Excellence with ACL Digital.

Scroll to Top