import { customElement, html, css, createSignal } from 'thunderous'; const MyElement = customElement(({ customCallback, adoptStyleSheet }) => { const [count, setCount] = createSignal(0); const increment = customCallback(() => setCount(count() + 1)); adoptStyleSheet(myStyleSheet); return html` <button onclick="${increment}">Increment</button> <output>${count}</output> `; }); const myStyleSheet = css` :host { display: block; font-family: sans-serif; } `; MyElement.define('my-element');

Why Thunderous?

Unlike web component frameworks, the Thunderous library doesn't impose a particular approach to building entire applications. Instead, it provides a set of tools to help you build interoperable web components that can be used in any framework or vanilla JavaScript. Thunderous works best for building component libraries as design systems. Components may be distributed through standalone packages to be consumed by multiple projects. It's designed to be agnostic, even offering a standard way to defer the tag name definitions until the components are used by the consumer, with registries. The biggest advantage Thunderous provides over similar libraries is its "render-once" approach and fine-grained reactivity; signals bind to templates and apply changes directly where needed. Putting aside the performance benefits, this approach also avoids the complexity and cognitive burden of additional lifecycle methods. It's a more natural way to write JavaScript.

Why Functions?

With the rising popularity of functional components in modern frameworks, Thunderous provides a familiar and more approachable DX for web components - a common complaint among developers. This shared experience can largely be attributed to the fact that functions are more concise and easier to reason about than classes. For example:
  • There's less boilerplate code to write
  • One scope is easier to manage than several smaller scopes
  • Class methods are in danger of losing their binding context
  • Repetition may be necessary for each method's scope
  • Encapsulation tends to be stronger in function closures than in classes
  • Functions are more predictable and less prone to side effects
While functions may deviate from the native web component API, Thunderous is actually designed to keep the developer closer to the web platform than other libraries. It avoids deep inheritance chains that introduce extra behavior; each component only builds upon the native HTMLElement. There's no build step required, so you can integrate Thunderous into your existing projects without any hassle.
import { customElement, html, createSignal } from 'thunderous'; import { getCallback, BaseElement } from './utils'; // Thunderous functional component: const FnElement = customElement(({ customCallback }) => { const [count, setCount] = createSignal(0); const increment = customCallback(() => setCount(count() + 1)); return html` <button onclick="${increment}">Increment</button> <output>${count}</output> `; }); // Traditional class component with Thunderous helpers: class ClssElement extends BaseElement { #count = createSignal(0); increment() { const [count, setCount] = this.#count; setCount(count() + 1); } render() { const [count] = this.#count; const increment = getCallback('increment'); return html` <button onclick="${increment}">Increment</button> <output>${count}</output> `; } }

What about SSR?

A common concern with web components is server-side rendering (SSR). While Thunderous doesn't provide its own server, it does offer a way to transform response text before it's sent back to the client. When a Thunderous component is defined on the server, its function runs once to return a plain HTML string using declarative shadow DOM if necessary. With the onServerDefine() and insertTemplates() functions, you can inject that content into the server rendered HTML.

Contributing

Thunderous is still in its early stages, but it's already being used in production. The library is being actively developed and maintained, with new features and improvements being added regularly. If you're interested in contributing, please check out the GitHub repository and feel free to open an issue or submit a pull request.