Skip to content

Quick start

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

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 ReactiveProps<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