Targ Apps Docs
Chroma

Introduction

Chroma is a VDOM-free web framework whose entire reactivity and DOM engine is written in Rust and compiled to WebAssembly.

What is Chroma

Chroma is a web framework for JavaScript/TypeScript developers whose entire engine — reactivity and DOM manipulation — is implemented in Rust and compiled to WebAssembly. UI is written with direct calls to e(tag, props, ...children), no JSX, no compiler, no build step.

import { hookState, render, e } from "./pkg/chroma.js";

function Counter() {
  const [count, setCount] = hookState(0);

  return e(
    "button",
    { onClick: () => setCount(count() + 1) },
    "Count: ",
    count,
  );
}

render(Counter, document.getElementById("app"));

Clicking the button updates only the text node bound to count — no re-render, no diffing, no VDOM.

Core principles

  • No VDOM, no diffing, no hydration. Each component runs exactly once. Reactive expressions stay bound to the exact DOM node they must update — fine-grained reactivity, SolidJS style.
  • The whole engine in Rust. Signals, dependency tracking, e, Fragment, and every DOM operation (via web-sys) live inside the .wasm binary. There is no shim or custom JS runtime: the only JavaScript in the package is the loader auto-generated by wasm-bindgen (pkg/chroma.js).
  • No build step, no transforms. No Babel, no mandatory bundlers, no template compilers. A <script type="module"> is all you need.

The one rule you must know

A child or an attribute is reactive only if you pass it as a function.

e("p", null, count)              // ✅ reactive: updates when count changes
e("p", null, count())            // ❌ static: evaluated once, never updates
e("p", null, () => count() * 2)  // ✅ reactive derived expression

There is no re-render that would re-evaluate JSX or expressions. The getter itself is the subscription — that's the entire reactivity model in one sentence. See Reactivity Model for the full mechanics.

What's implemented (v0.1)

  • Full hook set: hookState, useEffect, useLayoutEffect, useMemo, useCallback, useRef, useReducer, createContext / useContext, useDebugValue.
  • Control-flow components: Show, For.
  • Reusable components with variants: cva, cx.
  • Reactive text and attribute bindings.
  • render, e, Fragment.

Out of scope for now: SSR, keyed list reconciliation, DevTools. See Limitations.

Tooling

The engine alone is enough to ship an app (pkg/chroma.js + a static file server), but three sibling projects turn it into a full toolchain:

  • CLI (chroma.sh / chroma.ps1) — chroma install|new|dev|build. Provisions the toolchain, scaffolds a static or dynamic app template, serves it in development, and bundles it for release.
  • Chroma Server — the single-purpose static HTTP server the CLI installs and uses for chroma dev. Loopback-only, one thread per connection, zero dependencies beyond the standard library.
  • Chroma Desktop — a Tauri shell that imports chroma build zip bundles (drag-and-drop or file picker) and runs them locally: each import is extracted into its own folder and served from a local-only port, with previously imported apps listed on relaunch.
  • Chroma-Fs — a TypeScript storage plugin with IndexedDB and Tauri filesystem backends behind one file API.

Where to go next

On this page