Architecture
How Chroma is structured - what runs in WebAssembly, where the JavaScript boundary is, and how a state change reaches the DOM.
Chroma's engine is Rust compiled to WebAssembly. It owns signals, dependency tracking, element creation, event wiring, and every DOM operation. The only JavaScript in the package is the loader generated by wasm-bindgen (pkg/chroma.js), which instantiates the .wasm when you import it.
Overview
How a state change reaches the DOM
e("button", { onClick }, "Count: ", count)creates the element. Thecountgetter becomes a binding bound to one text slot.- While the binding evaluates, calling
count()subscribes it to the signal. - A click calls
setCount(count() + 1). The runtime compares withObject.is, stores the new value, and synchronously re-runs the subscribed bindings. - Each binding writes its result straight to its DOM target. No component function runs again and nothing is diffed.
The full mechanics are in Reactivity.
The WebAssembly boundary
Every DOM operation crosses from WebAssembly to JavaScript. This is a deliberate trade-off: the goal is an engine that lives entirely in Rust, not winning benchmarks against pure-JavaScript frameworks.
Browsers cannot import a .wasm file directly. ESM integration for WebAssembly is not shipped, and WebAssembly only exchanges numbers with JavaScript, so strings, objects, and callbacks need the wasm-bindgen bridge.
Error handling
- Misuse of the public API (a non-Element container in
render, an invalid tag ine, missing required props onShoworFor) throws an error with achroma:prefix. - Errors thrown inside your callbacks (effect bodies, cleanups, reducers, binding expressions,
Providerchildren) are caught and logged to the console, so one broken callback does not take down the rest of the tree. - Unexpected internal panics are logged with the name of the component being rendered, followed by a readable stack trace.
There are no error boundaries. See Limitations.
Two routers
Chroma has two things called createRouter:
- Screen router: exported by the WebAssembly engine (
createRouter,matchRoute,parseQuery). Hash or history navigation between screens, with a reactivecurrent()and aViewcomponent. - API router: exported by the
@chroma/routerplugin. File-based handlers underapi/that act as simulated endpoints in the browser.
See Routing for the API router and the API reference for the screen router.
The toolchain around the engine
| Piece | Role |
|---|---|
| CLI | Installs the toolchain, scaffolds apps, serves them in development, and builds them |
| Dev server | Loopback static server used by chroma dev |
| Desktop | Tauri shell that imports and runs chroma build bundles |
| Runtime modes | Portable (web) vs executable (Desktop) — shared WASM, different hosts |
| Plugins | Browser JavaScript plugins loaded at startup (storage, styling, API routing) |
Implementation details
The engine is organized into a pure dependency-graph module (no JavaScript or DOM awareness), a reactive runtime holding signal values and bindings, a DOM module for elements and events, and one module each for control flow, variants, the screen router, and the hooks. Control flow (Show, For) mounts content once and drives updates through an effect binding. The release profile is optimized for .wasm size; the loader's size is independent of it. Source: Chroma/src in the repository.