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
1 / 5

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) runs setup inside an effectScope, appends the returned node to target, and hands back a single unmount thunk. {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 on props arrives as a Computed<T> (callable getter). Return type is Element — a real DOM node.
  • Step 4 — class component. @reactive() turns class fields into signals with natural property access. render(): Element returns the same shape, called once when the instance mounts.
  • Step 5 — custom element. @attributes wires the static [attr] map so HTML attributes feed reactive properties. connectedCallback mounts; disconnectedCallback tears 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.

See also