Compare coding style, bundle size and performance of 59 different ways to make a Web Component.

Introduction

The same <my-counter/> Web Component has been written in 59 variants.

Table of Content

Source code and Bundle analysis

Select implementation:

HTMLElement

Homepage: https://html.spec.whatwg.org/multipage/custom-elements.html

Try in WebComponents.dev
const template = document.createElement('template');
template.innerHTML = `
  <style>
    * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 4rem;
      height: 4rem;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
  </style>
  <button id="dec">-</button>
  <span id="count"></span>
  <button id="inc">+</button>`;

class MyCounter extends HTMLElement {
  constructor() {
    super();
    this.count = 0;
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.shadowRoot.appendChild(template.content.cloneNode(true));
    this.shadowRoot.getElementById('inc').onclick = () => this.inc();
    this.shadowRoot.getElementById('dec').onclick = () => this.dec();
    this.update(this.count);
  }

  inc() {
    this.update(++this.count);
  }

  dec() {
    this.update(--this.count);
  }

  update(count) {
    this.shadowRoot.getElementById('count').innerHTML = count;
  }
}

customElements.define('my-counter', MyCounter);

No dependencies

Component size including library

Minified - Uncompressed 993
Minified + Gzip 505
Minified + Brotli 363

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

HTMLElement (w/ Alpine JS)

A rugged, minimal framework for composing JavaScript behavior in your markup.

Homepage: https://github.com/alpinejs/alpine

GitHub: https://github.com/alpinejs/alpine

Try in WebComponents.dev
import Alpine from 'alpinejs'

const template = document.createElement("template");
template.innerHTML = `
  <style>
    span, button {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 4rem;
      height: 4rem;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
  </style>
  <div x-data="$el.parentElement.data()">
    <button @click="dec()">-</button>
    <span x-text="count"></span>
    <button @click="inc()">+</button>
  </div>
`;

export class MyCounter extends HTMLElement {
  connectedCallback() {
    this.append(template.content.cloneNode(true));
  }

  data() {
    return {
      count: 0,
      inc() {
        this.count++;
      },
      dec() {
        this.count--;
      },
    };
  }
}

customElements.define("my-counter", MyCounter);
Alpine.start();

Dependencies

alpine :

Component size including library

Minified - Uncompressed 46,716
Minified + Gzip 17,583
Minified + Brotli 15,807

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

HTMLElement (w/ catalyst)

Catalyst is a set of patterns and techniques for developing components within a complex application. At its core, Catalyst simply provides a small library of functions to make developing Web Components easier.

Homepage: https://github.github.io/catalyst/

GitHub: https://github.com/github/catalyst

Try in WebComponents.dev
import { controller, target } from "@github/catalyst"

const template = document.createElement("template");
template.innerHTML = `<style>
  * {
    font-size: 200%;
  }

  span {
    width: 4rem;
    display: inline-block;
    text-align: center;
  }

  button {
    width: 4rem;
    height: 4rem;
    border: none;
    border-radius: 10px;
    background-color: seagreen;
    color: white;
  }
</style>
<button data-action="click:my-counter#dec">-</button>
<span data-target="my-counter.output"></span>
<button data-action="click:my-counter#inc">+</button>
`;

@controller
class MyCounterElement extends HTMLElement {
  @target output: HTMLElement;

  count: number = 0;

  connectedCallback() {
    this.attachShadow({ mode: "open" });
    this.shadowRoot.appendChild(template.content.cloneNode(true));
    this.update();
  }

  inc() {
    this.count++;
    this.update();
  }

  dec() {
    this.count--;
    this.update();
  }

  update() {
    this.output.textContent = "" + this.count;
  }
}

Dependencies

@github/catalyst : ^1.1.4

Component size including library

Minified - Uncompressed 4,545
Minified + Gzip 1,869
Minified + Brotli 1,597

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

HTMLElement (w/ inc.-dom)

Homepage: http://google.github.io/incremental-dom/

GitHub: https://github.com/google/incremental-dom

Try in WebComponents.dev
import { elementOpen, elementClose, text, patch } from "incremental-dom";

class MyCounter extends HTMLElement {
  constructor() {
    super();
    this.count = 0;
    this.attachShadow({ mode: "open" });
  }

  get styles() {
    return `
    	span,button {
    		font-size: 200%;
    	}

    	span {
    		width: 4rem;
    		display: inline-block;
    		text-align: center;
    	}

    	button {
    		width: 4rem;
    		height: 4rem;
    		border: none;
    		border-radius: 10px;
    		background-color: seagreen;
    		color: white;
    	}`;
  }

  connectedCallback() {
    this.update();
  }

  inc() {
    this.count++;
    this.update();
  }

  dec() {
    this.count--;
    this.update();
  }

  update() {
    patch(this.shadowRoot, () => {
      elementOpen("style");
      text(this.styles);
      elementClose("style");

      elementOpen("button", null, ["onclick", this.dec.bind(this)]);
      text("-");
      elementClose("button");

      elementOpen("span");
      text(this.count);
      elementClose("span");

      elementOpen("button", null, ["onclick", this.inc.bind(this)]);
      text("+");
      elementClose("button");
    });
  }
}

customElements.define("my-counter", MyCounter);

Dependencies

incremental-dom : ^0.7.0

Component size including library

Minified - Uncompressed 7,565
Minified + Gzip 3,137
Minified + Brotli 2,792

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

HTMLElement (w/ jtml)

@github/jtml - This library is designed as a layer on top of @github/template-parts to provide declarative, JavaScript based HTML template tags.

Homepage: https://www.npmjs.com/package/@github/jtml

Try in WebComponents.dev
import { html, render } from "@github/jtml";

class MyCounter extends HTMLElement {
  constructor() {
    super();
    this.count = 0;
  }

  connectedCallback() {
    this.attachShadow({mode: 'open'})
    this.update()
  }

  inc() {
    this.count++;
    this.update();
  }

  dec() {
    this.count--;
    this.update();
  }

  update() {
    render(html`
      <style>
        * {
          font-size: 200%;
        }

        span {
          width: 4rem;
          display: inline-block;
          text-align: center;
        }

        button {
          width: 64px;
          height: 64px;
          border: none;
          border-radius: 10px;
          background-color: seagreen;
          color: white;
        }
      </style>
      <button onclick="${() => this.dec()}">-</button>
      <span>${this.count}</span>
      <button onclick="${() => this.inc()}">+</button>
    `,
    this.shadowRoot);
  }
}

customElements.define("my-counter", MyCounter);

Dependencies

@github/jtml : ^0.4.0

Component size including library

Minified - Uncompressed 7,066
Minified + Gzip 2,444
Minified + Brotli 2,139

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

HTMLElement (w/ Lighterhtml)

The hyperHTML strength & experience without its complexity

Homepage: https://medium.com/@WebReflection/lit-html-vs-hyperhtml-vs-lighterhtml-c084abfe1285

GitHub: https://github.com/WebReflection/lighterhtml

Try in WebComponents.dev
import { html, render } from 'lighterhtml';

class MyCounter extends HTMLElement {
  constructor() {
    super();
    this.count = 0;
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.update();
  }

  inc = () => {
    this.count++;
    this.update();
  };

  dec = () => {
    this.count--;
    this.update();
  };

  style() {
    return `
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    `;
  }

  template() {
    return html`
      <style>
        ${this.style()}
      </style>
      <button onclick="${this.dec}">-</button>
      <span>${this.count}</span>
      <button onclick="${this.inc}">+</button>
    `;
  }

  update() {
    render(this.shadowRoot, this.template());
  }
}

customElements.define('my-counter', MyCounter);

Dependencies

lighterhtml : ^4.2.0

Component size including library

Minified - Uncompressed 12,215
Minified + Gzip 5,281
Minified + Brotli 4,774

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

HTMLElement (w/ Lit 2.0)

Lit - Simple. Fast. Web Components.

Homepage: https://lit.dev

GitHub: https://github.com/lit/lit

Try in WebComponents.dev
import { html, render } from 'lit';

class MyCounter extends HTMLElement {
  constructor() {
    super();
    this.count = 0;
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.update();
  }

  inc() {
    this.count++;
    this.update();
  }

  dec() {
    this.count--;
    this.update();
  }

  style() {
    return /*css*/ `
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    `;
  }

  template() {
    return html`
      <style>
        ${this.style()}
      </style>
      <button @click="${this.dec}">-</button>
      <span>${this.count}</span>
      <button @click="${this.inc}">+</button>
    `;
  }

  update() {
    render(this.template(), this.shadowRoot, { host: this });
  }
}

customElements.define('my-counter', MyCounter);

Dependencies

lit : ^2.0.2

Component size including library

Minified - Uncompressed 16,127
Minified + Gzip 5,801
Minified + Brotli 5,236

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

HTMLElement (w/ Lit-html)

Lit-Html - An efficient, expressive, extensible HTML templating library for JavaScript

Homepage: https://lit-html.polymer-project.org/

GitHub: https://github.com/polymer/lit-html

Try in WebComponents.dev
import { html, render } from 'lit-html';

class MyCounter extends HTMLElement {
  constructor() {
    super();
    this.count = 0;
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.update();
  }

  inc() {
    this.count++;
    this.update();
  }

  dec() {
    this.count--;
    this.update();
  }

  style() {
    return `
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    `;
  }

  template() {
    return html`
      <style>
        ${this.style()}
      </style>
      <button @click="${this.dec}">-</button>
      <span>${this.count}</span>
      <button @click="${this.inc}">+</button>
    `;
  }

  update() {
    render(this.template(), this.shadowRoot, { host: this });
  }
}

customElements.define('my-counter', MyCounter);

Dependencies

lit-html : ^2.0.2

Component size including library

Minified - Uncompressed 8,590
Minified + Gzip 3,520
Minified + Brotli 3,161

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

HTMLElement (w/ Mithril)

A JavaScript Framework for Building Brilliant Applications

Homepage: https://mithril.js.org/

GitHub: https://github.com/MithrilJS/mithril.js

Try in WebComponents.dev
/* @jsx m */
import m from "mithril";

export class MyCounter extends HTMLElement {
  count: number = 0;

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

  get styles() {
    return (
      <style>{`
    	span,button {
    		font-size: 200%;
    	}

    	span {
    		width: 4rem;
    		display: inline-block;
    		text-align: center;
    	}

    	button {
    		width: 4rem;
    		height: 4rem;
    		border: none;
    		border-radius: 10px;
    		background-color: seagreen;
    		color: white;
    	}`}</style>
    );
  }

  inc() {
    this.count++;
    this.update();
  }

  dec() {
    this.count--;
    this.update();
  }

  connectedCallback() {
    this.update();
  }

  update() {
    m.render(
      this.shadowRoot as any,
      <div>
        {this.styles}
        <button onclick={this.dec.bind(this)}>-</button>
        <span>{this.count}</span>
        <button onclick={this.inc.bind(this)}>+</button>
      </div>
    );
  }
}

customElements.define("my-counter", MyCounter);

Dependencies

mithril : ^2.0.4

Component size including library

Minified - Uncompressed 28,344
Minified + Gzip 10,160
Minified + Brotli 9,101

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

HTMLElement (w/ Sidewind)

Sidewind - Tailwind but for state

Homepage: https://sidewind.js.org/

GitHub: https://github.com/survivejs/sidewind

Try in WebComponents.dev
import 'sidewind';

const template = document.createElement("template");
template.innerHTML = `
  <style>
    span, button {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 4rem;
      height: 4rem;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
  </style>
  <div x-state="0">
    <button onclick="setState(counter => counter - 1)"> - </button>
    <span x="state"></span>
    <button onclick="setState(counter=> counter + 1)"> + </button>
  </div>
`;

export class MyCounter extends HTMLElement {
  connectedCallback() {
    this.append(template.content.cloneNode(true));
    evaluateAllDirectives()
  }
}

customElements.define("my-counter", MyCounter);

Dependencies

sidewind : ^5.4.6

Component size including library

Minified - Uncompressed 7,892
Minified + Gzip 2,949
Minified + Brotli 2,583

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

HTMLElement (w/ uhtml)

micro html is a ~2.5K lighterhtml subset to build declarative and reactive UI via template literals tags.

Homepage: https://github.com/WebReflection/uhtml

GitHub: https://github.com/WebReflection/uhtml

Try in WebComponents.dev
import { html, render } from "uhtml";

class MyCounter extends HTMLElement {
  constructor() {
    super();
    this.count = 0;
    this.attachShadow({ mode: "open" });
  }

  connectedCallback() {
    this.update();
  }

  inc = () => {
    this.count++;
    this.update();
  };

  dec = () => {
    this.count--;
    this.update();
  };

  template() {
    return html`
      <style>
        * {
          font-size: 200%;
        }

        span {
          width: 4rem;
          display: inline-block;
          text-align: center;
        }

        button {
          width: 64px;
          height: 64px;
          border: none;
          border-radius: 10px;
          background-color: seagreen;
          color: white;
        }
      </style>
      <button onclick="${this.dec}">-</button>
      <span>${this.count}</span>
      <button onclick="${this.inc}">+</button>
    `;
  }

  update() {
    render(this.shadowRoot, this.template());
  }
}

customElements.define("my-counter", MyCounter);

Dependencies

uhtml : ^2.7.6

Component size including library

Minified - Uncompressed 7,349
Minified + Gzip 3,330
Minified + Brotli 2,988

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

AppRun

AppRun is a JavaScript library for building reliable, high-performance web applications using the Elm inspired architecture, events, and components.

Homepage: https://apprun.js.org/

GitHub: https://github.com/yysun/apprun

Try in WebComponents.dev
/** @jsx app.h */
import { app, Component } from "apprun";

const styles = `.my-counter * {
  font-size: 200%;
}

.my-counter span {
  width: 4rem;
  display: inline-block;
  text-align: center;
}

.my-counter button {
  width: 4rem;
  height: 4rem;
  border: none;
  border-radius: 10px;
  background-color: seagreen;
  color: white;
}`;

const add = (state, num) => state + num;

class Counter extends Component {
  state = 0;
  view = state => <div class="my-counter">
    <style>{styles}</style>
    <button $onclick={[add, -1]}>-</button>
    <span>{state}</span>
    <button $onclick={[add, +1]}>+</button>
  </div>;
}
app.webComponent('my-counter', Counter);

Dependencies

apprun : ^3.28.7

Component size including library

Minified - Uncompressed 15,537
Minified + Gzip 5,344
Minified + Brotli 4,755

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

CanJS

Build CRUD apps in fewer lines of code.

Homepage: https://canjs.com/

GitHub: https://github.com/canjs/canjs

Try in WebComponents.dev
import { StacheElement } from 'can/core';

class MyCounter extends StacheElement {
  constructor() {
    super();
    this.viewRoot = this.attachShadow({ mode: 'open' });
  }

  static view = `<style>
* {
  font-size: 200%;
}

span {
  width: 4rem;
  display: inline-block;
  text-align: center;
}

button {
  width: 64px;
  height: 64px;
  border: none;
  border-radius: 10px;
  background-color: seagreen;
  color: white;
}
</style>
<button on:click="this.decrement()">-</button>
<span>{{ this.count }}</span>
<button on:click="this.increment()">+</button>`;

  static props = {
    count: 0,
  };

  increment() {
    this.count++;
  }

  decrement() {
    this.count--;
  }
}

customElements.define('my-counter', MyCounter);

Dependencies

can : ^6.6.1

Component size including library

Minified - Uncompressed 347,264
Minified + Gzip 97,490
Minified + Brotli 83,377

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

ElemX

Library for connecting MobX to native Web Components with a Vue-like template binding syntax

Homepage: https://github.com/agquick/elemx.js

GitHub: https://github.com/agquick/elemx.js

Try in WebComponents.dev
import { observable, makeObservable, action } from "mobx";
import { ReactiveElement } from "elemx";

class MyCounterElement extends ReactiveElement {
  counter = 0;

  constructor() {
    super();
    makeObservable(this, {
      counter: observable,
      inc: action,
      dec: action
    })
  }

  templateHTML() {
    return `
    <button @on-click="this.dec">-</button>
    {{this.counter}}
    <button @on-click="this.inc">+</button>
    `;
  }

  inc() {
    this.counter++;
  }

  dec() {
    this.counter--;
  }

  templateCSS() {
    return `
  * {
		font-size: 200%;
	}

	span {
		width: 4rem;
		display: inline-block;
		text-align: center;
	}

	button {
		width: 4rem;
		height: 4rem;
		border: none;
		border-radius: 10px;
		background-color: seagreen;
		color: white;
	}
    `;
  }
}
customElements.define("my-counter", MyCounterElement);

Dependencies

elemx : ^0.2.2
mobx : ^6.3.6

Component size including library

Minified - Uncompressed 102,024
Minified + Gzip 28,859
Minified + Brotli 22,910

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Exalt

A JavaScript framework for building universal apps.

GitHub: https://github.com/exalt/exalt

Try in WebComponents.dev
import { Component, html } from "@exalt/core";
import { define } from "@exalt/core/decorators";

const style = /*css*/`
* {
  font-size: 200%;
}

span {
  width: 4rem;
  display: inline-block;
  text-align: center;
}

button {
  width: 4rem;
  height: 4rem;
  border: none;
  border-radius: 10px;
  background-color: seagreen;
  color: white;
}
`;

@define({ tag: "my-counter", styles: [style] })
export class MyCounter extends Component {

  counter = super.reactive(0);

  render() {
    return html`
      <button onclick=${() => this.counter--}>-</button>
      <span>${this.counter}</span>
      <button onclick=${() => this.counter++}>+</button>
    `;
  }
}

Dependencies

@exalt/core : ^1.1.4

Component size including library

Minified - Uncompressed 6,717
Minified + Gzip 2,774
Minified + Brotli 2,442

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

FAST

The adaptive interface system for modern web experiences

Homepage: https://www.fast.design/

GitHub: https://github.com/microsoft/fast

Try in WebComponents.dev
import { html, css, FASTElement } from "@microsoft/fast-element";

class MyCounter extends FASTElement {
  static definition = {
    name: "my-counter",
    template: html<MyCounter>`
      <button @click="${(x) => x.count--}">-</button>
      <span>${(x) => x.count}</span>
      <button @click="${(x) => x.count++}">+</button>
    `,
    styles: css`
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    `,
    attributes: ["count"],
  };

  count: number = 0;
}

FASTElement.define(MyCounter);

Dependencies

@microsoft/fast-element : ^1.6.2

Component size including library

Minified - Uncompressed 24,559
Minified + Gzip 7,662
Minified + Brotli 6,876

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Compiled and/or Transpiled by WebComponents.dev compiler.

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

HyperHTML Element

Framework agnostic, hyperHTML can be used to render any view, including Custom Elements and Web Components.

GitHub: https://github.com/WebReflection/hyperHTML-Element

Try in WebComponents.dev
import HyperHTMLElement from "hyperhtml-element";

class MyCounter extends HyperHTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  created() {
    this.count = 0;
    this.render();
  }

  inc = () => {
    this.count++;
    this.render();
  };

  dec = () => {
    this.count--;
    this.render();
  };

  render() {
    return this.html`
    <style>
      * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
    </style>
    <button onclick=${this.dec}>-</button>
    <span>${this.count}</span>
    <button onclick=${this.inc}>+</button>
    `;
  }
}

MyCounter.define("my-counter");

Dependencies

hyperhtml-element : ^3.15.1

Component size including library

Minified - Uncompressed 22,995
Minified + Gzip 9,185
Minified + Brotli 8,387

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Joist

A small (~2kb) library to help with the creation of web components and web component based applications

Homepage: https://github.com/joist-framework/joist

GitHub: https://github.com/joist-framework/joist

Try in WebComponents.dev
// By Danny Blue GitHub:deebloo
import { component, JoistElement, handle, get, State } from "@joist/component";

@component({
  tagName: "my-counter",
  shadowDom: "open",
  styles: [
    `* {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 4rem;
      height: 4rem;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }`,
  ],
  state: 0,
  render: ({ state, host, run }) => {
    const self = host.shadowRoot;
    if (!self.innerHTML) {
      self.innerHTML = `
        <button id="dec">-</button>
        <span id="count"></span>
        <button id="inc">+</button>`;
      self.getElementById("inc").addEventListener("click", run("add", 1));
      self.getElementById("dec").addEventListener("click", run("add", -1));
    }
    self.getElementById("count").innerHTML = state.toString();
  },
})
export class CounterElement extends JoistElement {
  @get(State) private state!: State<number>;

  @handle("add") add(_: Event, value: number) {
    this.state.setValue(this.state.value + value);
  }
}

Dependencies

@joist/component : ^1.8.8

Component size including library

Minified - Uncompressed 6,037
Minified + Gzip 2,217
Minified + Brotli 1,946

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Joist (with lit-html)

A small (~2kb) library to help with the creation of web components and web component based applications

Homepage: https://github.com/joist-framework/joist

GitHub: https://github.com/joist-framework/joist

Try in WebComponents.dev
// By Danny Blue GitHub:deebloo

import { component, JoistElement, handle, get, State } from "@joist/component";
import { template } from "@joist/component/lit-html";
import { html } from "lit-html";

@component({
  tagName: "my-counter",
  shadowDom: "open",
  state: 0,
  styles: [
    `* {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 4rem;
      height: 4rem;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }`,
  ],
  render: template(({ state, run }) => {
    return html`
      <button @click="${run("update", -1)}">-</button>
      <span>${state}</span>
      <button @click="${run("update", 1)}">+</button>
    `;
  }),
})
export class CounterElement extends JoistElement {
  @get(State) private state!: State<number>;

  @handle("update")
  updateCount(_: Event, num: number) {
    return this.state.setValue(this.state.value + num);
  }
}

Dependencies

@joist/component : ^1.8.8
lit-html : ^2.0.2

Component size including library

Minified - Uncompressed 13,608
Minified + Gzip 5,111
Minified + Brotli 4,635

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Lit 2.0 JavaScript

Lit - Simple. Fast. Web Components.

Homepage: https://lit.dev/

GitHub: https://github.com/lit/lit

Try in WebComponents.dev
import { LitElement, html, css } from "lit";

export class MyCounter extends LitElement {
  static properties = {
    count: { type: Number },
  };

  static styles = css`
    * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
  `;

  constructor() {
    super();
    this.count = 0;
  }

  inc() {
    this.count++;
  }

  dec() {
    this.count--;
  }

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

customElements.define("my-counter", MyCounter);

Dependencies

lit : ^2.0.2

Component size including library

Minified - Uncompressed 16,251
Minified + Gzip 5,941
Minified + Brotli 5,347

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Lit 2.0 TypeScript

Lit - Simple. Fast. Web Components.

Homepage: https://lit.dev/

GitHub: https://github.com/lit/lit

Try in WebComponents.dev
import {html, css, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';

@customElement('my-counter')
export class SimpleGreeting extends LitElement {
  @property() public count = 0;

  static styles = css`
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 4rem;
        height: 4rem;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }`;

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

  inc() {
    this.count++;
  }

  dec() {
    this.count--;
  }

}

Dependencies

lit : ^2.0.2

Component size including library

Minified - Uncompressed 17,360
Minified + Gzip 6,316
Minified + Brotli 5,671

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

LitElement v2

Homepage: https://lit-element.polymer-project.org/

GitHub: https://github.com/polymer/lit-element

Try in WebComponents.dev
import { LitElement, html, css } from 'lit-element';

export class MyCounter extends LitElement {
  static properties = {
    count: { type: Number },
  };

  static styles = css`
    * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
  `;

  constructor() {
    super();
    this.count = 0;
  }

  inc() {
    this.count++;
  }

  dec() {
    this.count--;
  }

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

customElements.define('my-counter', MyCounter);

Dependencies

lit-element : ^3.0.2

Component size including library

Minified - Uncompressed 16,520
Minified + Gzip 6,098
Minified + Brotli 5,460

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Neow (Alpha)

Modern, fast, and light it's the front-end framework to fall inlove with. (ALPHA)

Homepage: https://neow.dev

GitHub: (alpha)

Try in WebComponents.dev
import { ComponentMixin } from "@neow/core";

class MyComponent extends ComponentMixin(HTMLElement) {
  static template = `
  <style>
    * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
  </style>
  <button onclick="{{this.dec()}}">-</button>
  <span>{{this.counter}}</span>
  <button onclick="{{this.inc()}}">+</button>
`;

  counter = 0;

  inc() {
    this.counter++;
  }

  dec() {
    this.counter--;
  }
}

customElements.define("my-counter", MyComponent);

Dependencies

@neow/core : ^1.0.1

Component size including library

Minified - Uncompressed 4,415
Minified + Gzip 1,993
Minified + Brotli 1,719

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Omi w/Class

Front End Cross-Frameworks Framework - Merge Web Components, JSX, Virtual DOM, Functional style, observe or Proxy into one framework with tiny size and high performance. Write components once, using in everywhere, such as Omi, React, Preact, Vue or Angular.

Homepage: http://omijs.org

GitHub: https://github.com/Tencent/omi

Try in WebComponents.dev
/** @jsx h */
import { WeElement, define, h } from "omi";

define(
  "my-counter",
  class extends WeElement {
    data = {
      count: 0,
    };

    static css = `
    * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }`;

    dec = () => {
      this.data.count--;
      this.update();
    };

    inc = () => {
      this.data.count++;
      this.update();
    };

    render() {
      return (
        <h.f>
          <button onClick={this.dec}>-</button>
          <span>{this.data.count}</span>
          <button onClick={this.inc}>+</button>
        </h.f>
      );
    }
  }
);

Dependencies

omi : ^6.23.0

Component size including library

Minified - Uncompressed 20,841
Minified + Gzip 7,233
Minified + Brotli 6,511

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Readymade

JavaScript microlibrary for developing Web Components with Decorators

Homepage: https://github.com/readymade-ui/readymade

GitHub: https://github.com/readymade-ui/readymade

Try in WebComponents.dev
import { Component, State, CustomElement, html, css } from '@readymade/core';

@Component({
  selector: 'my-counter',
  template: html`
    <button id="dec">-</button>
    <span>{{ c }}</span>
    <button id="inc">+</button>
  `,
  style: css`
	span,
	button {
		font-size: 200%;
	}
	span {
		width: 4rem;
		display: inline-block;
		text-align: center;
	}
	button {
		width: 4rem;
		height: 4rem;
		border: none;
		border-radius: 10px;
		background-color: seagreen;
		color: white;
	}
	`
})
export class MyCounter extends CustomElement {
  private $state: {
    c: 0;
  };
  connectedCallback() {
    this.shadowRoot
      .querySelector('#inc')
      .addEventListener('click', this.inc.bind(this));
    this.shadowRoot
      .querySelector('#dec')
      .addEventListener('click', this.dec.bind(this));
  }

  get count() {
    return this.$state.c;
  }

  inc() {
    this.setState('c', this.count + 1);
  }

  dec() {
    this.setState('c', this.count - 1);
  }

  @State()
  public getState() {
    return { c: 0 };
  }
}

Dependencies

@readymade/core : ^2.0.1

Component size including library

Minified - Uncompressed 7,133
Minified + Gzip 2,925
Minified + Brotli 2,574

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

SkateJS (with Lit-html)

SkateJS - Effortless custom elements powered by modern view libraries.

Homepage: https://skatejs.netlify.com/

GitHub: https://github.com/skatejs/skatejs

Try in WebComponents.dev
import Element from "@skatejs/element";
import { render, html } from "lit-html";

class MyCounterElement extends Element {
  static get props() {
    return {
      count: Number,
    };
  }

  inc = () => {
    this.count++;
  };

  dec = () => {
    this.count--;
  };

  render() {
    const style = `
    * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }`;

    return html`
      <style>
        ${style}
      </style>
      <button @click="${this.dec}">
        -
      </button>
      <span>${this.count}</span>
      <button @click="${this.inc}">
        +
      </button>
    `;
  }

  renderer() {
    return render(this.render(), this.renderRoot);
  }
}

customElements.define("my-counter", MyCounterElement);

Dependencies

@skatejs/element : 0.0.1
lit-html : ^2.0.2

Component size including library

Minified - Uncompressed 13,916
Minified + Gzip 5,131
Minified + Brotli 4,630

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

SkateJS (with Preact)

SkateJS - Effortless custom elements powered by modern view libraries.

Homepage: https://skatejs.netlify.com/

GitHub: https://github.com/skatejs/skatejs

Try in WebComponents.dev
/** @jsx h **/
import Element, { h } from "@skatejs/element-preact";

class MyCounterElement extends Element {
  static get props() {
    return {
      count: Number,
    };
  }

  inc = () => {
    this.count++;
  };

  dec = () => {
    this.count--;
  };

  render() {
    const style = `host * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }`;

    return (
      <host>
        <style>{style}</style>
        <button onclick={this.dec}>-</button>
        <span>{this.count}</span>
        <button onclick={this.inc}>+</button>
      </host>
    );
  }
}

customElements.define("my-counter", MyCounterElement);

Dependencies

@skatejs/element-preact : 0.0.1

Component size including library

Minified - Uncompressed 17,386
Minified + Gzip 6,229
Minified + Brotli 5,679

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

SlimJS

Slim.js is a lightning fast library for development of native Web Components and Custom Elements based on modern standards. No black magic involved, no useless dependencies.

Homepage: https://slimjs.com

GitHub: https://github.com/slimjs/slim.js

Try in WebComponents.dev
import { Slim } from 'slim-js';
import { tag, template } from 'slim-js/decorators';
import 'slim-js/directives';

@tag("my-counter")
@template(`
<style>
  * {
    font-size: 200%;
  }

  span {
    width: 4rem;
    display: inline-block;
    text-align: center;
  }

  button {
    width: 64px;
    height: 64px;
    border: none;
    border-radius: 10px;
    background-color: seagreen;
    color: white;
  }
</style >
<button @click="this.dec()">-</button>
<span>{{this.count}}</span>
<button @click="this.inc()">+</button>
`)
class MyCounter extends Slim {
  count = 0;
  
  inc () {
    this.count++
  }

  dec () {
    this.count--
  }
}

Dependencies

slim-js : ^5.0.10

Component size including library

Minified - Uncompressed 14,271
Minified + Gzip 4,962
Minified + Brotli 4,175

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

uce-template

A tiny toolless library with tools included

Homepage: https://medium.com/@WebReflection/a-new-web-components-wonder-f9e042785a91

GitHub: https://github.com/WebReflection/uce-template

Try in WebComponents.dev
<my-counter>
  <button onclick="{{dec}}">
    -
  </button>
  <span>{{state.count}}</span>
  <button onclick="{{inc}}">
    +
  </button>
</my-counter>

<script type="module">
  import reactive from "@uce/reactive";
  export default {
    setup(element) {
      const state = reactive({ count: 0 });
      const inc = () => {
        state.count++;
      };
      const dec = () => {
        state.count--;
      };
      return { state, inc, dec };
    },
  };
</script>

<style scoped>
  * {
    font-size: 200%;
  }

  span {
    width: 4rem;
    display: inline-block;
    text-align: center;
  }

  button {
    width: 4rem;
    height: 4rem;
    border: none;
    border-radius: 10px;
    background-color: seagreen;
    color: white;
  }
</style>

Dependencies

uce-template : ^0.7.1

Component size including library

Minified - Uncompressed 25,845
Minified + Gzip 10,232
Minified + Brotli 9,261

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Wafer

A simple and lightweight base library for building Web Components that can be used on the browser, server or both

Homepage: https://waferlib.netlify.app/

GitHub: https://github.com/lamplightdev/wafer

Try in WebComponents.dev
/**
 * Uses Wafer (https://waferlib.netlify.app/)
 */

import Wafer from "@lamplightdev/wafer";

export class MyCounter extends Wafer {
  static get template() {
    return `
      <style>
        * {
          font-size: 200%;
        }

        span {
          width: 4rem;
          display: inline-block;
          text-align: center;
        }

        button {
          width: 64px;
          height: 64px;
          border: none;
          border-radius: 10px;
          background-color: seagreen;
          color: white;
        }
      </style>

      <button id="dec">-</button>
      <span id="count"></span>
      <button id="inc">+</button>
    `;
  }

  static get props() {
    return {
      count: {
        type: Number,
        initial: 0,
        targets: [{
          selector: '$#count',
          text: true,
        }]
      }
    }
  }

  get events() {
    return {
      '$#inc': {
        click: () => this.count++
      },
      '$#dec': {
        click: () => this.count--
      }
    }
  }
}

customElements.define("my-counter", MyCounter);

Dependencies

@lamplightdev/wafer : ^1.0.14

Component size including library

Minified - Uncompressed 6,392
Minified + Gzip 2,349
Minified + Brotli 2,059

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Atomico

Atomico is a microlibrary (3.9kB) inspired by React Hooks, designed and optimized for the creation of small, powerful, declarative webcomponents and only using functions

Homepage: https://atomico.gitbook.io/doc/

GitHub: https://github.com/atomicojs/atomico

Try in WebComponents.dev
import { c, useProp } from "atomico";

const style = /*css*/ `
  * {
    font-size: 200%;
  }

  span {
    width: 4rem;
    display: inline-block;
    text-align: center;
  }

  button {
    width: 4rem;
    height: 4rem;
    border: none;
    border-radius: 10px;
    background-color: seagreen;
    color: white;
  }
`;

function myCounter() {
  let [count, setCount] = useProp("count");

  return (
    <host shadowDom>
      <style>{style}</style>
      <button onclick={() => setCount(count - 1)}>-</button>
      <span>{count}</span>
      <button onclick={() => setCount(count + 1)}>+</button>
    </host>
  );
}

myCounter.props = {
  count: {
    type: Number,
    reflect: true,
    value: 0,
  },
};

customElements.define("my-counter", c(myCounter));

Dependencies

atomico : ^1.34.0

Component size including library

Minified - Uncompressed 8,355
Minified + Gzip 3,928
Minified + Brotli 3,514

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


{
  "presets": [
    [
      "@babel/preset-react",
      {
        "throwIfNamespace": false,
        "runtime": "automatic",
        "importSource": "atomico"
      }
    ]
  ]
}

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Gallop

Use full power of web components

GitHub: https://github.com/tarnishablec/gallop

Try in WebComponents.dev
import { component, css, useState, useStyle, html } from "@gallop/gallop";

component("my-counter", () => {
  const [state] = useState({ count: 0 });
  useStyle(
    () => css`
      * {
        font-size: 200%;
      }
      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }
      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    `,
    []
  );
  return html`
    <button @click="${() => state.count--}">
      -
    </button>
    <span>${state.count}</span>
    <button @click="${() => state.count++}">
      +
    </button>
  `;
});

Dependencies

@gallop/gallop : ^1.0.13

Component size including library

Minified - Uncompressed 11,147
Minified + Gzip 4,286
Minified + Brotli 3,838

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Haunted

React's Hooks API implemented for web components 👻

GitHub: https://github.com/matthewp/haunted

Try in WebComponents.dev
import { html, component, useState } from "haunted";

function Counter() {
  const [count, setCount] = useState(0);

  return html`
    <style>
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    </style>
    <button @click=${() => setCount(count - 1)}>
      -
    </button>
    <span>${count}</span>
    <button @click=${() => setCount(count + 1)}>
      +
    </button>
  `;
}

customElements.define("my-counter", component(Counter));

Dependencies

haunted : ^4.8.2

Component size including library

Minified - Uncompressed 15,890
Minified + Gzip 5,488
Minified + Brotli 4,906

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Heresy w/Hook

Don't simulate the DOM. Be the DOM. React-like Custom Elements via the V1 API built-in extends.

Homepage: https://medium.com/@WebReflection/any-holy-grail-for-web-components-c3d4973f3f3f

GitHub: https://github.com/WebReflection/heresy

Try in WebComponents.dev
import { define } from "heresy";

define("MyCounter", {
  style: (MyCounter) => `
    ${MyCounter} * {
      font-size: 200%;
    }
    ${MyCounter} span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }
    ${MyCounter} button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
  `,
  render({ useState }) {
    const [count, update] = useState(0);
    this.html`
      <button onclick="${() => update(count - 1)}">-</button>
      <span>${count}</span>
      <button onclick="${() => update(count + 1)}">+</button>
    `;
  },
});

Dependencies

heresy : ^1.0.4

Component size including library

Minified - Uncompressed 21,525
Minified + Gzip 8,797
Minified + Brotli 7,985

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Ficus w/ lit-html

Lightweight functions for developing applications using web components

Homepage: https://docs.ficusjs.org/

GitHub: https://github.com/ficusjs/ficusjs

Try in WebComponents.dev
import { render as renderer, html } from "lit-html";
import { createComponent } from "ficusjs";

createComponent("my-counter", {
  root: "shadow",
  state() {
    return { count: 0 };
  },
  renderer,
  inc() {
    this.state.count++;
  },
  dec() {
    this.state.count--;
  },
  render() {
    return html`
      <style>
        span,
        button {
          font-size: 200%;
        }

        span {
          width: 4rem;
          display: inline-block;
          text-align: center;
        }

        button {
          width: 4rem;
          height: 4rem;
          border: none;
          border-radius: 10px;
          background-color: seagreen;
          color: white;
        }
      </style>
      <div>
        <button @click="${this.dec}">-</button>
        <span>${this.state.count}</span>
        <button @click="${this.inc}">+</button>
      </div>
    `;
  },
});

Dependencies

ficusjs : ^3.9.0
lit-html : ^2.0.2

Component size including library

Minified - Uncompressed 14,416
Minified + Gzip 5,368
Minified + Brotli 4,807

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Heresy

Don't simulate the DOM. Be the DOM. React-like Custom Elements via the V1 API built-in extends.

Homepage: https://medium.com/@WebReflection/any-holy-grail-for-web-components-c3d4973f3f3f

GitHub: https://github.com/WebReflection/heresy

Try in WebComponents.dev
import { define } from "heresy";

define("MyCounter", {
  style: (MyCounter) => `
    ${MyCounter} * {
      font-size: 200%;
    }
    ${MyCounter} span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }
    ${MyCounter} button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
  `,
  oninit() {
    this.count = 0;
  },
  onclick({ currentTarget }) {
    this[currentTarget.dataset.op]();
    this.render();
  },
  inc() {
    this.count++;
  },
  dec() {
    this.count--;
  },
  render() {
    this.html`
      <button data-op="dec" onclick="${this}">-</button>
      <span>${this.count}</span>
      <button data-op="inc" onclick="${this}">+</button>
    `;
  },
});

Dependencies

heresy : ^1.0.4

Component size including library

Minified - Uncompressed 21,648
Minified + Gzip 8,837
Minified + Brotli 8,020

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

htmlhammer

Write HTML with JavaScript using real HTML tag names.

GitHub: https://github.com/vsmid/htmlhammer

Try in WebComponents.dev
import { style, button, span, customElement } from 'htmlhammer';

const MyCounterStyle = style(/*css*/`
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 4rem;
        height: 4rem;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    `);

const MyCounter = customElement("my-counter", {
  Count: 0,
  CounterDisplay: null,  
  connectedCallback() {
    this.attachShadow({ mode: "open" });
    this.shadowRoot.append(
      MyCounterStyle,
      button({ id: "dec", onclick: this.Dec }, "-"),
      this.CounterDisplay = span(this.Count),
      button({ id: "inc", onclick: this.Inc }, "+")
    );
  },
  Inc() {
    this.Update(++this.Count);
  },
  Dec() {
    this.Update(--this.Count);
  },
  Update(count) {
    this.CounterDisplay.textContent = count;
  }
});

Dependencies

htmlhammer : ^3.5.2

Component size including library

Minified - Uncompressed 5,970
Minified + Gzip 2,769
Minified + Brotli 2,340

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Hybrids

Hybrids is a UI library for creating web components with strong declarative and functional approach based on plain objects and pure functions.

Homepage: https://hybrids.js.org

GitHub: https://github.com/hybridsjs/hybrids

Try in WebComponents.dev
import { html, define } from "hybrids";

function inc(host) {
  host.count++;
}

function dec(host) {
  host.count--;
}

const MyCounter = {
  count: 0,
  render: ({ count }) => html`
    <style>
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    </style>
    <button onclick="${dec}">-</button>
    <span>${count}</span>
    <button onclick="${inc}">+</button>
  `,
};

define("my-counter", MyCounter);

Dependencies

hybrids : ^6.1.0

Component size including library

Minified - Uncompressed 16,582
Minified + Gzip 6,315
Minified + Brotli 5,688

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Kirei

An in browser implementation of Vue 3 (limited to Composition API)

GitHub: https://github.com/ifaxity/kirei

Try in WebComponents.dev
import { defineComponent, html, css, ref } from '@kirei/element';

defineComponent({
  name: 'MyCounter',
  props: {},
  styles: css`
    span, button {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 4rem;
      height: 4rem;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
  `,
  setup(props, ctx) {
    const count = ref(0);

    function dec() {
      count.value--;
    }

    function inc() {
      count.value++;
    }

    return () => html`
      <button @click=${dec}>-</button>
      <span>${count}</span>
      <button @click=${inc}>+</button>
    `;
  }
});

Dependencies

@kirei/element : ^2.0.4

Component size including library

Minified - Uncompressed 29,009
Minified + Gzip 11,305
Minified + Brotli 10,241

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Litedom

A very small (3kb) view library that is packed: Web Components, Custom Element, Template Literals, Reactive, Data Binding, One Way Data Flow, Two-way data binding, Event Handling, Props, Lifecycle, State Management, Computed Properties, Directives. No dependencies, no virtual dom, no build tool. Wow! ...but it's a just a view library!

Homepage: https://litedom.js.org/

GitHub: https://github.com/mardix/litedom

Try in WebComponents.dev
import Litedom from "litedom";

Litedom({
  tagName: "my-counter",
  shadowDOM: true,
  template: `
  <button @click="dec">-</button>
  <span>{this.count}</span>
  <button @click="inc">+</button>
  <style>
    * {
      font-size: 200%;
    }

    span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }
  </style>
  `,
  data: {
    count: 0,
  },
  dec() {
    this.data.count--;
  },
  inc() {
    this.data.count++;
  },
});

Dependencies

litedom : ^0.12.1

Component size including library

Minified - Uncompressed 9,476
Minified + Gzip 3,855
Minified + Brotli 3,421

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Ottavino

Tiny, Fast and Declarative User Interface Development. Using native custom elements API (but not only). As simple as it gets.

GitHub: https://github.com/betterthancode/ottavino

Try in WebComponents.dev
import { component } from "ottavino";

component({
  tag: "my-counter",
  shadow: true,
  template: `
    <style>
      * {
        font-size: 200%;
      }

      span {
        width: 4rem;
        display: inline-block;
        text-align: center;
      }

      button {
        width: 64px;
        height: 64px;
        border: none;
        border-radius: 10px;
        background-color: seagreen;
        color: white;
      }
    </style>
    <button onclick="{{this.decrease()}}">-</button>
    <span>{{this.count}}</span>
    <button onclick="{{this.increase()}}" >+</button>
    `,
  properties: {
    count: 0,
  },
  this: {
    increase: function () {
      this.count++;
    },
    decrease: function () {
      this.count--;
    },
  },
});

Dependencies

ottavino : ^0.2.4

Component size including library

Minified - Uncompressed 4,474
Minified + Gzip 1,984
Minified + Brotli 1,719

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Swiss

Functional custom elements

GitHub: https://github.com/luwes/swiss

Try in WebComponents.dev
import { define } from "swiss";
import { html, render } from "lit-html";

const Counter = (CE) => (el) => {
  el.attachShadow({ mode: "open" });

  return {
    update: () =>
      render(
        html`
          <style>
            * {
              font-size: 200%;
            }

            span {
              width: 4rem;
              display: inline-block;
              text-align: center;
            }

            button {
              width: 4rem;
              height: 4rem;
              border: none;
              border-radius: 10px;
              background-color: seagreen;
              color: white;
            }
          </style>
          <button @click="${() => el.count--}">-</button>
          <span>${el.count}</span>
          <button @click="${() => el.count++}">+</button>
        `,
        el.shadowRoot
      ),
  };
};

define("my-counter", {
  props: { count: 0 },
  setup: Counter,
});

Dependencies

swiss : ^2.4.0

Component size including library

Minified - Uncompressed 11,263
Minified + Gzip 4,520
Minified + Brotli 4,072

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Synergy

Synergy.js is a 4kB runtime library that gives you everything you need to define Web Components using standard HTML, JavaScript and CSS.

Homepage: https://synergyjs.org/

GitHub: https://github.com/defx/synergy

Try in WebComponents.dev
import { define } from 'synergy';

const style = /*css*/`
  * {
		font-size: 200%;
	}

	span {
		width: 4rem;
		display: inline-block;
		text-align: center;
	}

	button {
		width: 4rem;
		height: 4rem;
		border: none;
		border-radius: 10px;
		background-color: seagreen;
		color: white;
	}`;

const template = /*css*/`
    <style>${style}</style>
    <button onclick="dec()">
      -
    </button>
		<span>{{ count }}</span>
		<button onclick="inc()">
      +
    </button>`;

const model = ({ count = 0 }) => {
  return { count, 
           inc() {
             this.count++;
           }, 
           dec() {
             this.count--;
           } 
  };
};

define("my-counter", model, template, { shadow:  'open'  });

Dependencies

synergy : ^5.0.0

Component size including library

Minified - Uncompressed 10,001
Minified + Gzip 4,148
Minified + Brotli 3,711

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

uce

µhtml based Custom Elements.

Homepage: https://github.com/WebReflection/uce

GitHub: https://github.com/WebReflection/uce

Try in WebComponents.dev
import "uce";

const MyCounter = ({ define }) => {
  define("my-counter", {
    attachShadow: { mode: "open" },
    props: { count: 0 },
    bound: ["inc", "dec"],
    inc() {
      this.count++;
      this.render();
    },
    dec() {
      this.count--;
      this.render();
    },
    render() {
      this.html`
        <style>
        * {
          font-size: 200%;
        }
        span {
          width: 4rem;
          display: inline-block;
          text-align: center;
        }
        button {
          width: 64px;
          height: 64px;
          border: none;
          border-radius: 10px;
          background-color: seagreen;
          color: white;
        }
        </style>
        <button onclick="${this.dec}">-</button>
        <span>${this.count}</span>
        <button onclick="${this.inc}">+</button>
      `;
    },
  });
};

customElements
  .whenDefined("uce-lib")
  .then(() => MyCounter(customElements.get("uce-lib")));

Dependencies

uce : ^1.16.5

Component size including library

Minified - Uncompressed 10,398
Minified + Gzip 4,601
Minified + Brotli 4,172

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

twind

Homepage: https://twind.dev/

GitHub: https://github.com/tw-in-js/twind

Try in WebComponents.dev
import { create, cssomSheet } from 'twind'

// 1. Create separate CSSStyleSheet
const sheet = cssomSheet({ target: new CSSStyleSheet() })

// 2. Use that to create an own twind instance
const { tw } = create({ sheet })

const template = document.createElement("template");
template.innerHTML = /*html*/`
  <p class="${tw`invisible`}">This uses CSSStyleSheet but<br>it's not supported in Firefox.</p> 
  <button class="${tw`rounded-lg px-7 py-4 text(white 3xl) bg-green-600`}" id="dec">-</button>
  <span class="${tw`inline-block w-14 text(3xl center)`}" id="count"></span>
  <button class="${tw`rounded-lg px-7 py-4 text(white 3xl) bg-green-600`}" id="inc">+</button>`;

class MyCounter extends HTMLElement {
  constructor() {
    super();
    this.count = 0;
    this.attachShadow({ mode: "open" });
    this.shadowRoot.adoptedStyleSheets = [sheet.target];
  }

  connectedCallback() {
    this.shadowRoot.appendChild(template.content.cloneNode(true));
    this.shadowRoot.getElementById("inc").onclick = () => this.inc();
    this.shadowRoot.getElementById("dec").onclick = () => this.dec();
    this.update(this.count);
  }

  inc() {
    this.update(++this.count);
  }

  dec() {
    this.update(--this.count);
  }

  update(count) {
    this.shadowRoot.getElementById("count").innerHTML = count;
  }
}

customElements.define("my-counter", MyCounter);

Dependencies

twind : ^0.16.16

Component size including library

Minified - Uncompressed 35,589
Minified + Gzip 14,200
Minified + Brotli 12,719

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

twind + LitElement

twind - A small compiler that turns Tailwind short hand into CSS rules at run, build or serve time. If you have used Tailwind and/or a CSS-in-JS solution before then most of the API will feel very familiar.

Homepage: https://twind.dev/

GitHub: https://github.com/tw-in-js/twind

Try in WebComponents.dev
import { LitElement, html } from "lit";
import { create, cssomSheet } from 'twind'

// 1. Create separate CSSStyleSheet
const sheet = cssomSheet({ target: new CSSStyleSheet() })

// 2. Use that to create an own twind instance
const { tw } = create({ sheet })

export class MyCounter extends LitElement {
  static properties = {
    count: { type: Number },
  };

  static styles = [sheet.target];

  constructor() {
    super();
    this.count = 0;
  }

  inc() {
    this.count++;
  }

  dec() {
    this.count--;
  }

  render() {
    return html`
      <p class="${tw`invisible`}">This uses CSSStyleSheet but<br>it's not supported in Firefox.</p> 
      <button class="${tw`rounded-lg px-7 py-4 text(white 3xl) bg-green-600`}" @click="${this.dec}">-</button>
      <span class="${tw`inline-block w-14 text(3xl center)`}">${this.count}</span>
      <button class="${tw`rounded-lg px-7 py-4 text(white 3xl) bg-green-600`}" @click="${this.inc}">+</button>
    `;
  }
}

customElements.define("my-counter", MyCounter);

Dependencies

twind : ^0.16.16
lit-element : ^3.0.2

Component size including library

Minified - Uncompressed 51,133
Minified + Gzip 19,406
Minified + Brotli 17,531

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


{
  "plugins": [
    [
      "@babel/plugin-proposal-decorators",
      {
        "decoratorsBeforeExport": true
      }
    ],
    "@babel/plugin-proposal-class-properties"
  ]
}

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Lightning Web Components

⚡️ LWC - A Blazing Fast, Enterprise-Grade Web Components Foundation

Homepage: https://lwc.dev

GitHub: https://github.com/salesforce/lwc

Try in WebComponents.dev
import { LightningElement } from "lwc";

export default class MyCounter extends LightningElement {
  count = 0;

  inc() {
    this.count++;
  }

  dec() {
    this.count--;
  }
}

customElements.define("my-counter", MyCounter.CustomElementConstructor);

Dependencies

lwc : ^2.5.10

Component size including library

Minified - Uncompressed 44,904
Minified + Gzip 14,991
Minified + Brotli 13,431

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following LWC settings:

const outputConfig: {
  format: 'esm',
  sourcemap: true,
};

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Lume Element

Create Custom Elements with reactivity and automatic re-rendering

GitHub: https://github.com/lume/element

Try in WebComponents.dev
import {Element, reactify} from '@lume/element'
import {html} from '@lume/element/dist/html'

export class MyCounter extends Element {
	count = 0;

	constructor() {
		super()
		reactify(this, ['count'])
	}

	template = () => html`
		<button onclick=${() => (this.count -= 1)}>-</button>
		<span>${() => this.count}</span>
		<button onclick=${() => (this.count += 1)}>+</button>
	`

	css = /*css*/ `
		* { font-size: 200%; }

		span {
			width: 4rem;
			display: inline-block;
			text-align: center;
		}

		button {
			width: 4rem; height: 4rem;
			border: none;
			border-radius: 10px;
			background-color: seagreen;
			color: white;
			outline: none;
			cursor: pointer;
		}
	`
}

customElements.define('my-counter', MyCounter)

Dependencies

@lume/element : ^0.5.6

Component size including library

Minified - Uncompressed 57,094
Minified + Gzip 19,867
Minified + Brotli 18,055

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the solid preset:

{
  "presets": [
    "solid"
  ]
}

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Prism

Prism is a experimental compiler for building isomorphic web applications with web components.

GitHub: https://github.com/kaleidawave/prism

Try in WebComponents.dev
<template>
	<button @click="dec">
		-
	</button>
	<span>{count}</span>
	<button @click="inc">
		+
	</button>
</template>

<script>
	@Default({count: 0})
    class MyCounter extends Component<{count: number}> {
        inc() {
            this.data.count++;
        }

        dec() {
            this.data.count--;
        }
    }
</script>

<style>
	* {
		font-size: 200%;
	}

	span {
		width: 4rem;
		display: inline-block;
		text-align: center;
	}

	button {
		width: 4rem;
		height: 4rem;
		border: none;
		border-radius: 10px;
		background-color: seagreen;
		color: white;
	}
</style>

Dependencies

@kaleidawave/prism :

Component size including library

Minified - Uncompressed 1,869
Minified + Gzip 961
Minified + Brotli 793

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Solid Element

The Deceptively Simple User Interface Library

GitHub: https://github.com/ryansolid/solid

Try in WebComponents.dev
import { createSignal } from "solid-js";
import { customElement } from "solid-element";

const style = `div * {
          font-size: 200%;
        }

        span {
          width: 4rem;
          display: inline-block;
          text-align: center;
        }

        button {
          width: 4rem;
          height: 4rem;
          border: none;
          border-radius: 10px;
          background-color: seagreen;
          color: white;
        }`;

customElement("my-counter", () => {
  const [count, setCount] = createSignal(0);
  return (
    <div>
      <style>{style}</style>
      <button onClick={() => setCount(count() - 1)}>-</button>
      <span>{count}</span>
      <button onClick={() => setCount(count() + 1)}>+</button>
    </div>
  );
});

Dependencies

solid-js : ^1.2.3
solid-element : ^1.2.3

Component size including library

Minified - Uncompressed 10,829
Minified + Gzip 4,146
Minified + Brotli 3,693

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the solid preset:

{
  "presets": [
    "solid"
  ]
}

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Stencil

Stencil is a toolchain for building reusable, scalable Design Systems. Generate small, blazing fast, and 100% standards based Web Components that run in every browser.

Homepage: https://stenciljs.com/

GitHub: https://github.com/ionic-team/stencil

Try in WebComponents.dev
/* @jsx h */
import { Component, Prop, h, Host } from '@stencil/core';

@Component({
  tag: "my-counter",
  styleUrl: "index.css",
  shadow: true,
})
export class MyCounter {
  @Prop() count: number = 0;

  inc() {
    this.count++;
  }

  dec() {
    this.count--;
  }

  render() {
    return (
      <Host>
        <button onClick={this.dec.bind(this)}>-</button>
        <span>{this.count}</span>
        <button onClick={this.inc.bind(this)}>+</button>
      </Host>
    );
  }
}

Dependencies

@stencil/core : ^2.10.0

Component size including library

Minified - Uncompressed 13,803
Minified + Gzip 5,737
Minified + Brotli 5,177

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been compiled with Stencil CLI and settings:

// stencil.tsconfig.js
...
"compilerOptions": {
  "allowSyntheticDefaultImports": true,
  "allowUnreachableCode": false,
  "declaration": false,
  "experimentalDecorators": true,
  "lib": ["DOM", "ES2018"],
  "moduleResolution": "node",
  "module": "esnext",
  "target": "es2017",
  "noUnusedLocals": true,
  "noUnusedParameters": true,
  "jsx": "react",
  "jsxFactory": "h",
  "types": []
}
...

//stencil.config.js
import { Config } from '@stencil/core';

export const config: Config = {
  srcDir: 'stencil/src',
  namespace: 'my-counter',
  tsconfig: 'stencil.tsconfig.json',
  hashFileNames: false,
  plugins: [],
  outputTargets: [
    {
      type: 'dist-custom-elements-bundle',
    },
  ],
  hydratedFlag: null,
  extras: {
    cssVarsShim: false,
    dynamicImportShim: false,
    safari10: false,
    scriptDataOpts: false,
    shadowDomShim: false,
  },
};

Bundling configuration

See details Bundled by Stencil CLI

Svelte (with {customElement: true})

Cybernetically enhanced web apps

Homepage: https://svelte.dev/

GitHub: https://github.com/sveltejs/svelte

Try in WebComponents.dev
<svelte:options tag="my-counter" />

<script>
	let count = 0;

	function inc() {
		count++;
	}

	function dec() {
		count--;
	}
</script>

<style>
	* {
		font-size: 200%;
	}

	span {
		width: 4rem;
		display: inline-block;
		text-align: center;
	}

	button {
		width: 64px;
		height: 64px;
		border: none;
		border-radius: 10px;
		background-color: seagreen;
		color: white;
	}
</style>

<button on:click={dec}>
	-
</button>
<span>{count}</span>
<button on:click={inc}>
	+
</button>

Dependencies

svelte : ^3.44.1

Component size including library

Minified - Uncompressed 4,071
Minified + Gzip 1,911
Minified + Brotli 1,683

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Svelte settings:

const options = {
  format: 'esm',
  customElement: true
};

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Preact w/Class (wrapped with preact-custom-element)

Fast 3kB alternative to React with the same modern API.

Homepage: https://preactjs.com/

GitHub: https://github.com/preactjs/preact

Try in WebComponents.dev
import { createCustomElement } from "@wcd/preact-custom-element";
import { Component, html } from "htm/preact";
import "preact";

class MyCounter extends Component {
  state = {
    count: 0,
  };

  inc = () => {
    this.setState((prev) => ({ count: prev.count + 1 }));
  };

  dec = () => {
    this.setState((prev) => ({ count: prev.count - 1 }));
  };

  render(props, state) {
    return html`
      <style>
        * {
          font-size: 200%;
        }

        span {
          width: 4rem;
          display: inline-block;
          text-align: center;
        }

        button {
          width: 64px;
          height: 64px;
          border: none;
          border-radius: 10px;
          background-color: seagreen;
          color: white;
        }
      </style>
      <button onClick=${this.dec}>
        -
      </button>
      <span>${state.count}</span>
      <button onClick=${this.inc}>
        +
      </button>
    `;
  }
}

customElements.define("my-counter", createCustomElement(MyCounter, ["count"]));

Dependencies

preact : ^10.5.15
@wcd/preact-custom-element : ^3.1.3

Component size including library

Minified - Uncompressed 11,982
Minified + Gzip 4,884
Minified + Brotli 4,461

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Babel settings:


[
  [
    Babel.availablePresets['env'],
    {
      targets: {
        browsers:
          'last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 iOS versions, last 2 Android versions',
      },
      modules: 'false',
      bugfixes: true,
    },
  ],
  [Babel.availablePresets['react']],
],
plugins: [
  [Babel.availablePlugins['proposal-decorators'], { legacy: true }],
  [Babel.availablePlugins['proposal-class-properties'], { loose: true }],
]

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

React w/Class (wrapped with react-to-webcomponent)

A JavaScript library for building user interfaces

Homepage: https://reactjs.org/

GitHub: https://github.com/facebook/react/

Try in WebComponents.dev
import React from "react";
import ReactDOM from "react-dom";
import reactToWebComponent from "react-to-webcomponent";

interface State {
  count: number;
}
interface Props {}

export default class MyCounter extends React.Component<Props, State> {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  render() {
    const styles = `.my-counter * {
      font-size: 200%;
    }

    .my-counter span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    .my-counter button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }`;

    return (
      <div className="my-counter">
        <style>{styles}</style>
        <button onClick={() => this.setState({ count: this.state.count - 1 })}>
          -
        </button>
        <span>{this.state.count}</span>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          +
        </button>
      </div>
    );
  }
}

customElements.define(
  "my-counter",
  reactToWebComponent(MyCounter, React, ReactDOM)
);

Dependencies

react : ^17.0.2
react-dom : ^17.0.2
react-to-webcomponent : ^1.5.1

Component size including library

Minified - Uncompressed 131,814
Minified + Gzip 42,504
Minified + Brotli 37,361

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following TypeScript settings:
 {
  module: ts.ModuleKind.ESNext,
  experimentalDecorators: true,
  emitDecoratorMetadata: true,
  importHelpers: true,
  lib: ['ESNext', 'dom'],
  target: ts.ScriptTarget.ESNext
};

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

React w/Hook (wrapped with react-to-webcomponent)

A JavaScript library for building user interfaces

Homepage: https://reactjs.org/

GitHub: https://github.com/facebook/react/

Try in WebComponents.dev
import React, { useState } from "react";
import ReactDOM from "react-dom";
import reactToWebComponent from "react-to-webcomponent";

export default function MyCounter() {
  const [count, setCount] = useState(0);

  const styles = `
    .my-counter * {
      font-size: 200%;
    }

    .my-counter span {
      width: 4rem;
      display: inline-block;
      text-align: center;
    }

    .my-counter button {
      width: 64px;
      height: 64px;
      border: none;
      border-radius: 10px;
      background-color: seagreen;
      color: white;
    }`;

  return (
    <div className="my-counter">
      <style>{styles}</style>
      <button onClick={() => setCount(count - 1)}>-</button>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

customElements.define(
  "my-counter",
  reactToWebComponent(MyCounter, React, ReactDOM)
);

Dependencies

react : ^17.0.2
react-dom : ^17.0.2
react-to-webcomponent : ^1.5.1

Component size including library

Minified - Uncompressed 131,690
Minified + Gzip 42,479
Minified + Brotli 37,370

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following TypeScript settings:
 {
  module: ts.ModuleKind.ESNext,
  experimentalDecorators: true,
  emitDecoratorMetadata: true,
  importHelpers: true,
  lib: ['ESNext', 'dom'],
  target: ts.ScriptTarget.ESNext
};

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Riot (wrapped with @riotjs/custom-elements)

Simple and elegant component-based UI library

Homepage: https://riot.js.org/

GitHub: https://github.com/riot/riot

Try in WebComponents.dev
<my-component>
	<style>
		* {
			font-size: 200%;
		}

		span {
			width: 4rem;
			display: inline-block;
			text-align: center;
		}

		button {
			width: 64px;
			height: 64px;
			border: none;
			border-radius: 10px;
			background-color: seagreen;
			color: white;
		}
	</style>

	<button onclick={dec}>
		-
	</button>
	<span>{state.count}</span>
	<button onclick={inc}>
		+
	</button>

	<script>
		export default {
      onBeforeMount(props, state) {
        this.state = {
					count: 0
        }
      },
      inc() {
        this.update({
          count: this.state.count+1
        })
      },
      dec() {
        this.update({
          count: this.state.count-1
        })
      },
    }
	</script>
</my-component>

Dependencies

riot : ^6.0.4
@riotjs/custom-elements : ^6.0.0

Component size including library

Minified - Uncompressed 17,222
Minified + Gzip 6,218
Minified + Brotli 5,595

Composition

Open Bundle Visualizer

Compilation configuration

See details All Sources have been Transpiled by WebComponents.dev compiler with the following Riot settings:

const settings = {
  scopedCss: false,
};

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Vue2 (wrapped with vue-custom-element)

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

Homepage: https://vuejs.org/

GitHub: https://github.com/vuejs/vue

Try in WebComponents.dev
<template>
	<div>
		<button @click="this.dec">
					-
		</button>
		<span>{{count}}</span>
		<button @click="this.inc">
					+
		</button>
	</div>
</template>

<script>
	export default {
		tag: 'my-counter',
		name: 'MyCounter',
		data() {
			return { count: 0 }
		},
		methods: {
			inc: function() {
				this.count++;
			},
			dec: function() {
				this.count--;
			}
		}
	};
</script>

<style scoped>
	span,
	button {
		font-size: 200%;
	}

	span {
		width: 4rem;
		display: inline-block;
		text-align: center;
	}

	button {
		width: 64px;
		height: 64px;
		border: none;
		border-radius: 10px;
		background-color: seagreen;
		color: white;
	}
</style>

Dependencies

vue : ^2.6.14
vue-custom-element : ^3.3.0

Component size including library

Minified - Uncompressed 77,366
Minified + Gzip 27,334
Minified + Brotli 24,466

Composition

Open Bundle Visualizer

Compilation configuration

See details Vue sources have been Transpiled by rollup-plugin-vue

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Vue3 (wrapped within a minimum Web Component layer)

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

Homepage: https://vuejs.org/

GitHub: https://github.com/vuejs/vue

Try in WebComponents.dev
<template>
	<div>
		<button @click="dec">
      -
    </button>
		<span>{{ state.count }}</span>
		<button @click="inc">
      +
    </button>
	</div>
</template>

<script>
	import { reactive } from "vue";

export default {
  setup() {
    const state = reactive({
      count: 0,
    });

    function inc() {
      state.count++;
    }

    function dec() {
      state.count--;
    }

    return {
      state,
      inc,
      dec,
    };
  },
};
</script>

<style scoped>
	span,
	button {
		font-size: 200%;
	}

	span {
		width: 4rem;
		display: inline-block;
		text-align: center;
	}

	button {
		width: 4rem;
		height: 4rem;
		border: none;
		border-radius: 10px;
		background-color: seagreen;
		color: white;
	}
</style>

Dependencies

vue@next :

Component size including library

Minified - Uncompressed 50,782
Minified + Gzip 19,956
Minified + Brotli 18,090

Composition

Open Bundle Visualizer

Compilation configuration

See details Vue sources have been Transpiled by rollup-plugin-vue

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Vue3 CE (`defineCustomElement` +v3.2)

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

Homepage: https://vuejs.org/

GitHub: https://github.com/vuejs/vue

Try in WebComponents.dev
<template>
	<div>
		<button @click="dec">
      -
    </button>
		<span>{{ state.count }}</span>
		<button @click="inc">
      +
    </button>
	</div>
</template>

<script>
	import { reactive } from "vue";

export default {
  setup() {
    const state = reactive({
      count: 0,
    });

    function inc() {
      state.count++;
    }

    function dec() {
      state.count--;
    }

    return {
      state,
      inc,
      dec,
    };
  },
};
</script>

<style scoped>
	span,
	button {
		font-size: 200%;
	}

	span {
		width: 4rem;
		display: inline-block;
		text-align: center;
	}

	button {
		width: 4rem;
		height: 4rem;
		border: none;
		border-radius: 10px;
		background-color: seagreen;
		color: white;
	}
</style>

Dependencies

vue@next :

Component size including library

Minified - Uncompressed 52,575
Minified + Gzip 20,542
Minified + Brotli 18,571

Composition

Open Bundle Visualizer

Compilation configuration

See details Vue sources have been Transpiled by rollup-plugin-vue

Bundling configuration

See details Bundled with Rollup

import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

...

    input: 'source.js',
    output: {
      dir: 'dist'),
      format: 'esm',
    },
    plugins: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      withdeps ? resolve() : undefined,
      commonjs( /* { namedExports: for React } */ ),
      terser({ output: { comments: false } }),

...

Bundle Size Comparison

Bundle size of 1 component

If you only deliver a single <my-counter/> you have the full cost of the library for a single component. This is the total cost of a single component with the library included.

Notes

The bare HTMLElement component doesn't have any dependency and creates a unmatched tiny package.

Prism (experimental at this point) is the smallest package for a single component. Absolutely no runtime or dependency. Very impressive.

GitHub's Catalyst is an impressive tiny package but doesn't have a template rendering capability. You may need to add jtml (from GitHub), lit-html or uhtml.

Svelte is doing also very well with a very small library runtime cost due to excellent tree-shaking.

React, Vue2 and ElemX libraries go above the 20KB mark.

ElemX comes with MobX, it's powerful but it's not light.

Vue3 is delivering on promises with 30% improvement versus version 2.

The new Lit is smaller than the previous LitElement version by almost 20%.

The new Lit in TypeScript is bigger than the JavaScript due to the use of decorators (They do add quite some boiler plate code after transpilation).

Caution

Some libraries bring significant value and are more feature rich than others. It's normal to see larger sizes in these cases.

Nevertheless, for a simple counter component, we would like to see more tree-shaking happening.

On the other hand, once you start having multiple components using a broad spectrum of the library capabilities, you end up with the entire library anyway.

Estimated Bundle size of 30 components using the same library

This is an estimated size of a bundle of 30 <my-counter />-like components using the same library. All components will share the library code so the estimated size is calculated with: 1 bundle-with-dependencies + 29x components-without-dependencies.

Notes

It's interesting to see that some libraries managed to be smaller than bare HTMLElement. This is achieved by sharing more runtime accross the different components. Individual components are smaller and pay for the runtime. A library is not always an overhead !

The new comers Exalt, htmlhammer and Synergy are positioned right on the top. It seems that new libraries are more size concious right from the beginning.

Prism is not performing as well here because of the lack of library runtime. There is nothing to share between components.

Again here, Vue3 is delivering smaller compiled components and smaller runtime than Vue2

Performance

Performance of Parsing JavaScript + Create DOM tree

Benchmarked with Tachometer on Google Chrome Version Version 95.0.4638.69 (Official Build) (arm64).

The benchmark page is composed of 50 <my-counter /my-counter> laid out in a single page with the library bundle code inlined.

Notes

Take into consideration that there is a margin of error of 2ms.

Things that play a role in this performance figure are:

  • The size of the JavaScript to parse (more JavaScript = longer).
  • The performance of the library to create the DOM nodes from scratch.

Everything runs locally so the network download time is not taken into account here.

Everything runs on a relatively powerful laptop and results could be completely different on a lower-end device like a phone or a tablet.

Final Notes

It's hard to find so much diversity in a technical solution. It tells a lot about Web Components and the low level API provided. All the 59 variants could be on the same page if we wanted to!

On bundle size, the results are already very promising but there is still room for improvement:

  • Some libraries would benefit from splitting their features, to better benefit from tree-shaking.
  • Except Stencil and Svelte, none of the libraries offer CSS minification out of the box.

Web Components are well supported in browsers now. Still, for old browsers (including IE11), polyfills are required. The bundle sizes reported here are only for modern browsers that don't need any polyfills. It was too much additional work to include polyfills in this post. Maybe later... before they become obsolete.

We will report on new libraries and updates. Make sure to follow us on Twitter.

Feedback or Questions

Chat with us on our Discord or Twitter channels.

Thanks

Thanks to all those who helped us review and correct this analysis.

Changes

since May 2021 release

  • All libraries upgraded to minor versions
  • Libraries upgraded to major versions:
    • Apprun
    • Exalt
    • Gallop
    • LWC
    • Slimjs
  • Added incremental-dom
  • Added Sidewind
  • Added Vue +3.2 CE
  • Added Wafer
  • Re-adding CanJS
  • Removed Angular - Build issues with version 13

since January 2021 release

  • All libraries upgraded to @latest
  • Added htmlhammer
  • Added Exalt
  • Added Synergy
  • Added Prism compiler
  • Upgraded lit next-major to the new Lit (v2.0.0rc)
  • Updated uce-template counter with .uce
  • Updated Atomico counter
  • Updated Readymade counter
  • Removed CanJS - compilation issues

Brought to you by Twitter Discord