@stencil/core vs lit vs svelte
Building Reusable UI Components: Stencil vs Lit vs Svelte
@stencil/corelitsvelteSimilar Packages:

Building Reusable UI Components: Stencil vs Lit vs Svelte

@stencil/core, lit, and svelte are all tools for building modern user interfaces, but they target different architectural needs. @stencil/core is a compiler that generates standard Web Components, designed primarily for creating design systems that work across any framework. lit is a lightweight library for building Web Components using standard JavaScript and template literals, focusing on speed and alignment with web standards. svelte is a compiler that shifts work from the browser to the build step, generating highly optimized imperative code, and while it can output Web Components, it is primarily used as a standalone application framework.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
@stencil/core013,09023.1 MB179a month agoMIT
lit021,572106 kB6993 days agoBSD-3-Clause
svelte086,5772.84 MB1,0062 days agoMIT

Stencil vs Lit vs Svelte: Architecture, Reactivity, and Output Compared

When architecting a frontend system, the choice between @stencil/core, lit, and svelte often comes down to one key question: are you building a library for others to use, or an application for users to interact with? All three tools offer component-based development, but they solve different problems regarding distribution, runtime performance, and developer workflow. Let's break down how they handle the core mechanics of modern UI development.

๐Ÿ—๏ธ Component Definition & Templating

The way you define structure and logic varies significantly across these tools, impacting how developers onboard and how code is maintained.

@stencil/core uses JSX and TypeScript decorators.

  • It feels very similar to React for developers familiar with that ecosystem.
  • Requires a build step to transform decorators and JSX into standard JavaScript.
// stencil: src/components/my-component.tsx
import { Component, Prop, h } from '@stencil/core';

@Component({ tag: 'my-component' })
export class MyComponent {
  @Prop() name: string = 'World';

  render() {
    return <div>Hello, {this.name}!</div>;
  }
}

lit uses JavaScript classes with tagged template literals.

  • It relies on standard ES6 classes and no JSX transformation.
  • Templates are written directly in JavaScript using the html tag.
// lit: src/my-component.js
import { LitElement, html } from 'lit';
import { property } from 'lit/decorators.js';

export class MyComponent extends LitElement {
  @property() name = 'World';

  render() {
    return html`<div>Hello, ${this.name}!</div>`;
  }
}

svelte uses a single-file component format with HTML-like syntax.

  • Logic lives in a <script> tag, markup in the template, and styles in <style>.
  • No class boilerplate is required; the compiler handles the structure.
<!-- svelte: src/MyComponent.svelte -->
<script>
  export let name = 'World';
</script>

<div>Hello, {name}!</div>

<style>
  div { color: blue; }
</style>

โšก Reactivity & State Management

How the UI updates when data changes is critical for performance and code clarity. Each tool handles reactivity differently.

@stencil/core uses decorators to track state changes.

  • @State() triggers a re-render when internal data changes.
  • @Prop() handles external data passed into the component.
// stencil: State management
import { Component, State, Prop, h } from '@stencil/core';

@Component({ tag: 'counter-component' })
export class CounterComponent {
  @Prop() initialCount = 0;
  @State() count = 0;

  componentDidLoad() {
    this.count = this.initialCount;
  }

  render() {
    return <button onClick={() => this.count++}>{this.count}</button>;
  }
}

lit uses reactive properties that trigger updates automatically.

  • Properties defined with @property are observed for changes.
  • Updates are batched and asynchronous by default.
// lit: State management
import { LitElement, html } from 'lit';
import { property, state } from 'lit/decorators.js';

export class CounterComponent extends LitElement {
  @property({ type: Number }) initialCount = 0;
  @state() count = 0;

  firstUpdated() {
    this.count = this.initialCount;
  }

  render() {
    return html`<button @click=${() => this.count++}>${this.count}</button>`;
  }
}

svelte uses compile-time reactivity with assignment.

  • Simply assigning a new value triggers an update.
  • Reactive statements use the $: label for derived state.
<!-- svelte: State management -->
<script>
  export let initialCount = 0;
  let count = initialCount;
  
  $: doubled = count * 2;
</script>

<button on:click={() => count++}>{count} (x2: {doubled})</button>

๐ŸŽจ Styling & Shadow DOM

Encapsulation is a major reason developers choose Web Components. Here is how each tool handles CSS scoping.

@stencil/core enables Shadow DOM by default.

  • Styles defined in the component are scoped automatically.
  • Supports CSS variables for theming across the shadow boundary.
// stencil: Styling
@Component({
  tag: 'styled-component',
  styleUrl: 'styled-component.css',
  shadow: true
})
export class StyledComponent {
  render() {
    return <div class="container">Scoped Content</div>;
  }
}

lit supports Shadow DOM and provides helper functions for styles.

  • Uses static styles to define CSS within the class.
  • Ensures styles do not leak out or get affected by global CSS.
// lit: Styling
import { css } from 'lit';

export class StyledComponent extends LitElement {
  static styles = css`
    .container { color: red; }
  `;

  render() {
    return html`<div class="container">Scoped Content</div>`;
  }
}

svelte scopes CSS by default without Shadow DOM.

  • Adds unique classes to elements during compilation.
  • Can optionally compile to Web Components with Shadow DOM enabled.
<!-- svelte: Styling -->
<div class="container">Scoped Content</div>

<style>
  .container { color: red; }
  /* Compiler transforms this to .container.svelte-123xyz */
</style>

๐ŸŒ Framework Interoperability

One of the biggest architectural decisions is whether the component needs to work inside React, Angular, or Vue.

@stencil/core is built specifically for this use case.

  • Outputs standard Custom Elements that work everywhere.
  • Provides wrappers for React and Angular to handle event binding correctly.
// stencil: Output
// Compiles to:
// <my-component name="John"></my-component>
// Works in React, Angular, Vue, or plain HTML without extra deps.

lit outputs standard Custom Elements natively.

  • No wrappers are needed for framework integration.
  • Events and properties map directly to HTML attributes and DOM events.
// lit: Output
// Compiles to:
// <my-component name="John"></my-component>
// Directly usable in any framework that supports Custom Elements.

svelte primarily targets Svelte applications.

  • Can compile to Custom Elements using the customElements compiler option.
  • Requires specific build configuration to act as a library for other frameworks.
<!-- svelte: Output -->
<!-- Requires compiler config: { customElements: true } -->
<!-- Then usable as: <my-component name="John"></my-component> -->
<!-- Without config, it is a Svelte-specific module import. -->

๐Ÿ“Š Summary: Key Differences

Feature@stencil/corelitsvelte
Primary GoalDesign Systems & LibrariesWeb ComponentsApplication Framework
TemplatingJSX (TSX)Tagged Template LiteralsHTML-like Syntax
ReactivityDecorators (@State)Reactive PropertiesCompile-time Assignments
OutputStandard Web ComponentsStandard Web ComponentsJS Modules (or WC)
RuntimeSmall Runtime + Virtual DOMMinimal RuntimeNo Runtime (Compiled)
Learning CurveModerate (React-like)Low (Standard JS)Low (Unique Syntax)

๐Ÿ’ก The Big Picture

@stencil/core is the enterprise choice ๐Ÿข. It shines when you need to maintain a component library used by multiple teams working in different frameworks. The JSX support makes it familiar to React developers, and the robust tooling helps manage large-scale design systems. However, it adds build complexity that might be overkill for smaller projects.

lit is the standards choice ๐ŸŒ. It is ideal for developers who want the benefits of Web Components without a heavy abstraction layer. It is lightweight, fast, and stays close to the platform. Choose this when you want maximum compatibility and minimal lock-in to a specific toolchain.

svelte is the application choice ๐Ÿš€. It offers the best developer experience for building complete apps, with incredibly concise code and no virtual DOM overhead. While it can produce Web Components, that is not its primary strength. Use Svelte when you own the entire application stack and do not need to distribute components to non-Svelte projects.

Final Thought: If you are building a library, pick @stencil/core or lit. If you are building an app, pick svelte. Between the two library options, choose @stencil/core for React-like DX and lit for standard JavaScript simplicity.

How to Choose: @stencil/core vs lit vs svelte

  • @stencil/core:

    Choose @stencil/core if you are building a design system or component library that must be consumed by multiple frameworks like React, Angular, and Vue simultaneously. It is ideal for enterprise teams that need strong typing, JSX support, and features like server-side rendering or hydration out of the box for their Web Components. However, be aware that it introduces a build step complexity that is heavier than standard Web Component libraries.

  • lit:

    Choose lit if you want to build Web Components with minimal abstraction and maximum alignment with current browser standards. It is perfect for teams that prefer writing standard JavaScript or TypeScript without a heavy compiler toolchain or JSX transformation. Use this when you need small, fast components that integrate easily into any project without requiring a specific framework runtime.

  • svelte:

    Choose svelte if you are building a complete application and want a developer experience focused on writing less boilerplate code. It is best suited for projects where you control the entire stack and do not strictly require Web Component output for cross-framework usage. While it can compile to Web Components, its primary strength lies in its reactive compiler model for standalone apps rather than distributable component libraries.

README for @stencil/core

stencil-logo

Stencil

A compiler for generating Web Components using technologies like TypeScript and JSX, built by the Ionic team.

StencilJS is released under the MIT license. StencilJS is released under the MIT license. PRs welcome! Follow @stenciljs Official Ionic Discord

Quick Start ยท Documentation ยท Contribute ยท Blog
Community: Discord ยท Forums ยท Twitter

Getting Started

Start a new project by following our quick Getting Started guide. We would love to hear from you! If you have any feedback or run into issues using Stencil, please file an issue on this repository.

Examples

A Stencil component looks a lot like a class-based React component, with the addition of TypeScript decorators:

import { Component, Prop, h } from '@stencil/core';

@Component({
  tag: 'my-component',            // the name of the component's custom HTML tag
  styleUrl: 'my-component.css',   // css styles to apply to the component
  shadow: true,                   // this component uses the ShadowDOM
})
export class MyComponent {
  // The component accepts two arguments:
  @Prop() first: string;
  @Prop() last: string;

   //The following HTML is rendered when our component is used
  render() {
    return (
      <div>
        Hello, my name is {this.first} {this.last}
      </div>
    );
  }
}

The component above can be used like any other HTML element:

<my-component first="Stencil" last="JS"></my-component>

Since Stencil generates web components, they work in any major framework or with no framework at all. In many cases, Stencil can be used as a drop in replacement for traditional frontend framework, though using it as such is certainly not required.

Contributing

Thanks for your interest in contributing! Please take a moment to read up on our guidelines for contributing. We've created comprehensive technical documentation for contributors that explains Stencil's internal architecture, including the compiler, runtime, build system, and other core components in the /docs directory. Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.