Why ShadowJS
ShadowJS compiles JSX into direct DOM updates. A component runs once, and a change touches only the node that reads it.
Show the code
Counter.tsxtsx
import { useStore } from "@shadow-js/core";
export function Counter() {
const [count, setCount] = useStore(0);
const double = count() * 2; // stays live: the compiler derives it
return (
<button type="button" onClick={() => setCount((n) => n + 1)}>
{count()} x 2 = {double}
</button>
);
}
Click it. The button holds two pieces of text that read count() and double, and those two update. The Counter function is not called again and there is no tree to compare: the compiler turned your JSX into the exact DOM operations. The mental model shows how that works.
What you get#
- Fine-grained updates. Signals, memos and effects form a dependency graph. A write reaches only its readers, together, in one microtask.
- A real compiler.
@shadow-js/compilerreads JSX with a real parser and writes a static template plus one binding for each dynamic place. It targets the DOM and the server. - The DOM you wrote. No placeholder elements and no comment markers in the live page, so what you inspect is what you wrote.
- The same code on the server.
renderToStringmakes HTML andhydrateclaims it. The compiled client code is identical for rendering and hydrating. - Forgiving, where mistakes are common. A constant computed from a signal, a destructured prop or a function passed as a value still works. Forgiving reactivity lists every rule and where it stops.
- Types. Element and attribute types for HTML, SVG and MathML, and event handlers whose
currentTargethas the element's type.
What it is not#
- Not React. Nothing re-renders, there are no rules of hooks and no
keyprop (a keyed list uses<For key={...}>). Moving a React component means rewriting it around signals. Your app's structure and CSS carry over. - Not Solid with other names. It is the same family, with deliberate differences: the
use*names,useContextthrows when there is no provider and no default, and effects run in a microtask instead of after each write.
Next#
Quick start creates an app in a minute. Then State is the first page to read.