Starts like useState — state(0), .set, values straight in JSX. Then every change becomes an occurrence in a stream with transactional semantics, and undo, race-free search and cross-tab sync cost one fold each — not a library each.
Route every user action through one stream and fold it — undo, persistence, cross-tab sync and race-free search are one extra fold each, not a library each. External sources — storage events, sockets — enter the same model of moments.
Updates are transactions
Simultaneous changes are one atomic moment — a derived value can never observe half a state. Guaranteed by the engine's laws (classic FRP semantics), not by discipline.
No re-renders
A component function runs exactly once. State is a value you drop into JSX; only the text node or attribute that depends on it updates — no hooks, no dependency arrays, no virtual DOM.
Small enough to read
A complete app — framework, state and your code — builds to 5.6 kB of gzipped JS; React + ReactDOM alone are ~8× that. Hard size budgets are enforced in CI.
Are you an LLM? View /continuum/llms.txt for optimized Markdown documentation, or /continuum/llms-full.txt for full documentation bundle
Solid-class speed and memory — in a fraction of the bytes
Bundle size — full counter appkB gzip · lower is better
Continuum5.6 kB
Solid~7 kB
React + ReactDOM~45 kB
Memory — 10,000 rows, heap after GCMB · lower is better
−32 % memory, ~40 % less garbage, faster interaction — and one third the download. Measured on one machine through the same Playwright harness (npm run bench, bench:mem, size); full table with per-operation timings in the overview. React is a virtual-DOM re-render model — several times slower on the same table, and ~8× the bytes.
Edit the code, hit Run, and open the Compiled tab to see it become a parse-once template with a single insert hole. Then scaffold your own:
bash
npm create continuum-js@latest my-app
If you know useState, you already know this — except Counter never runs again. {count} binds a text node to the value; clicking patches exactly that node.
Everything above, Solid also promises. Here is the part it doesn't: change itself is a value. Route every change through one stream of actions and fold it — and features that are normally a library each become one line each:
tsx
const actions = stream<Action>();const todos = actions.accum(loadPersisted("todos", []), reduce); // the stateconst history = actions.accum(emptyHistory, undoReduce); // + undoonCleanup(persist("todos", todos)); // + persistence// the `storage` event dispatching into the same reducer: + cross-tab sync
And because requests are a stream too, search cannot race:
tsx
const results = resource(debounce(query.updates, 300), search);// a stale response physically cannot overwrite a fresh one
The reducer never changes. The patterns cookbook walks through all of these — undo, optimistic updates, race-free search — each as a few lines over the same stream.