Frameworks and libraries
- Lit (recommended)
- Stencil
- Fast Element (Microsoft)
- Haunted (hooks-based)
- Hybrids
- Atomico
Best for: Modern, TypeScript-first development
import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@customElement('lit-counter')
export class LitCounter extends LitElement {
@property({ type: Number })
count = 0;
static styles = css`
:host {
display: block;
padding: 1rem;
border: 1px solid #ddd;
}
button {
background: #007bff;
color: white;
border: none;
padding: 0.5rem 1rem;
cursor: pointer;
}
`;
render() {
return html`
<div>
<p>Count: ${this.count}</p>
<button @click=${this._increment}>Increment</button>
</div>
`;
}
private _increment() {
this.count++;
}
}
Lit provides excellent TypeScript support, a small bundle size (~5KB), and great developer experience with reactive properties and template literals with data binding. The main learning curve involves understanding TypeScript decorators.
Stencil is excellent for large-scale component libraries and provides a compiler-first approach:
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'stencil-counter',
styleUrl: 'counter.css',
shadow: true
})
export class Counter {
@Prop() count: number = 0;
increment() {
this.count = this.count + 1;
}
render() {
return (
<div>
<p>Count: {this.count}</p>
<button onClick={() => this.increment()}>Increment</button>
</div>
);
}
}
Stencil offers JSX syntax familiar to React developers, built-in compiler optimizations, and Angular-like decorators. It's particularly well-suited for building design systems but requires a build step and has a larger learning curve.
Fast Element is Microsoft's approach, focused on enterprise applications:
import { FASTElement, customElement, attr, html } from '@microsoft/fast-element';
@customElement('fast-counter')
export class Counter extends FASTElement {
@attr count = 0;
increment() {
this.count++;
}
}
Counter.define({
name: 'fast-counter',
template: html<Counter>`
<div>
<p>Count: ${x => x.count}</p>
<button @click=${x => x.increment()}>Increment</button>
</div>
`
});
Fast Element emphasizes high-performance and great accessibility support, making it suitable for enterprise applications. However, it has a smaller community and is more Microsoft-centric in its ecosystem.
Haunted provides a React-like hooks approach to web components:
import { component, useState } from 'haunted';
function Counter() {
const [count, setCount] = useState(0);
return html`
<div>
<p>Count: ${count}</p>
<button onclick=${() => setCount(count + 1)}>Increment</button>
</div>
`;
}
customElements.define('haunted-counter', component(Counter));
Haunted uses React-like hooks and is familiar to React developers, though it has a smaller ecosystem and less comprehensive TypeScript support.
Hybrids takes a functional programming approach:
import { define, html } from 'hybrids';
export const Counter = {
count: 0,
render: ({ count }) => html`
<div>
<p>Count: ${count}</p>
<button onclick=${host => { host.count += 1 }}>Increment</button>
</div>
`
};
define('hybrid-counter', Counter);
Atomico provides another hooks-based approach:
import { c, html } from 'atomico';
function Counter() {
const [count, setCount] = useHost();
return html`
<host count=${count}>
<p>Count: ${count}</p>
<button onclick=${() => setCount(count + 1)}>Increment</button>
</host>
`;
}
Counter.props = { count: Number };
customElements.define('atomico-counter', c(Counter));