Quick start
From signals to custom elements — build the same counter five different ways.
Start with signals, grow into a component, end as a native HTML tag. Each step below adds one layer on top of the previous — the reactive core never changes, only how you package it.
From signal to custom element
// 1. Just signals — reactive values, no DOM yet.
import { signal, computed } from "elements-kit/signals";
const count = signal(0);
const doubled = computed(() => count() * 2);
count(count() + 1); // write
console.log(count()); // read → 1
console.log(doubled()); // → 2
What's happening
- Step 1 — signals.
signal(0)holds state;computed(() => count() * 2)derives from it. Both are framework-agnostic — no DOM involved. - Step 2 — signals + JSX.
render(target, setup)runssetupinside aneffectScope, appends the returned node totarget, and hands back a singleunmountthunk.{count}creates a live text node that updates in place — no diffing, no re-render. - Step 3 — function component. Props are typed with
Props<T>; each key onpropsarrives as aComputed<T>(callable getter). Return type isElement— a real DOM node. - Step 4 — class component.
@reactive()turns class fields into signals with natural property access.render(): Elementreturns the same shape, called once when the instance mounts. - Step 5 — custom element.
@attributeswires thestatic [attr]map so HTML attributes feed reactive properties.connectedCallbackmounts;disconnectedCallbacktears down. Usable as a plain HTML tag in any framework or no framework at all.
The reactive core (steps 1–2) is doing all the work. Steps 3–5 are just packaging.
Next steps
- Learn the signal primitives in depth → Signals.
- Understand JSX → DOM compilation → Elements.
- Wrap the same pattern in a native custom element → Custom elements.