Targ Apps Docs
Chroma

Runtime modes

Portable (web) vs executable (Chroma Desktop) — same APIs and plugins, different compile targets and host behavior.

Chroma apps are always built from the same source: .chr components, chroma.json, and optional plugins. What changes is where the bundle runs and which host capabilities are available. The docs call those two deployment states Portable (Web) and Executable (Chroma Desktop).

The important idea: hooks, plugins, and public APIs are often callable from every entry point (browser tab, chroma dev, imported zip, static host). That does not mean they behave the same inside. Storage, origins, reload, and some plugin paths differ by runtime mode. Design and test against the modes you ship.

At a glance

Portable (Web)Executable (Chroma Desktop)
What you shipdist/ (HTML, JS bundle, WASM) on any static host or CDNSame dist/ (often as a .zip) plus the Chroma Desktop native app on the user's machine
EngineRust → WebAssembly in the browserSame WASM bundle, loaded in an iframe served over loopback HTTP
Typical devchroma dev --no-desktop → browser tabchroma dev with dev.desktop: true → Desktop develop window
Production openUser visits your URLUser installs Desktop and imports your zip (or you distribute a custom Tauri build)
Chroma-Fs defaultIndexedDB in the browser profileFilesystem bridge under app data when configured for desktop
Live reloadYes in the browser (Live reload)Develop mode does not auto-reload; restart chroma dev or re-import after chroma build

Both modes use the same chroma build output for dynamic apps. Portable mode is the usual choice for public sites; Executable mode is for offline-first or org-internal apps that should feel like a native program without maintaining a separate UI stack.

Portable (Web)

Portable means the app runs as a normal web application: the browser downloads your HTML, JavaScript bundle, and chroma_bg.wasm, then executes them in a tab or embedded WebView you do not control.

How you get there

  1. Develop with chroma dev --no-desktop (or dev.desktop: false in chroma.json).
  2. Run chroma validate and chroma build.
  3. Upload dist/ to static hosting or serve it with any HTTP server (Server reference).

The engine is compiled for the web WASM target (wasm-pack build --target web). The loader in pkg/chroma.js is the only JavaScript the engine ships; your app code is bundled beside it.

What behaves "web-native"

  • DOM and events — full browser APIs in the top-level page (no parent shell).
  • Plugins — browser ESM plugins load from plugins/loader.js; no Node.js at runtime.
  • API router — @chroma/router intercepts fetch in the page; handlers under api/ run locally (Routing).
  • Storage — Chroma-Fs uses IndexedDB unless you explicitly choose a backend that requires a host (see Backends).
  • Origins — cookies, localStorage, and IndexedDB are keyed to your deployment URL. Changing host or port starts a fresh storage partition.

Example: a counter app works identically in Portable mode; only deployment and persistence policies change.

// src/App.chr — same in both modes
import { hookState, e } from "../pkg/chroma.js";

export function App() {
  const [n, setN] = hookState(0);
  return e("button", { onClick: () => setN(n() + 1) }, "Count: ", n);
}

Executable (Chroma Desktop)

Executable here means the Chroma Desktop application (chroma-desktop): a Tauri-based native shell that hosts your same WASM bundle. The UI is still web technology; the executable is the installer users run on Linux or Windows.

Desktop is not a second framework. It is a loopback server plus window management, zip import, and bridges (for example Chroma-Fs over postMessage). Details: Desktop guide.

Two ways Desktop runs your app

ModeWhenURL shape
Developchroma dev with dev.desktop: trueDev server origin, e.g. http://127.0.0.1:<dev.port>/...
Library (imported zip)After chroma build and importhttp://127.0.0.1:47823/<app-id>/index.html by default

The iframe cannot call Tauri APIs directly. Plugins that need disk access use the Desktop bridge instead of IndexedDB when configured for filesystem storage.

What behaves "desktop-native"

  • Real files — with backend: "filesystem", data lands under <app-data>/apps/<app-id>/data/ (Desktop storage).
  • Stable library origin — Desktop tries to bind port 47823 so IndexedDB for imported apps survives restarts; if the port is busy, the origin may change (troubleshooting).
  • Develop vs browser — IndexedDB in a browser dev tab and in Desktop develop mode are different origins; do not assume shared data.
  • Security model — loopback-only serving and zip path checks; treat imported bundles like any native program (Security notes).

chroma install can place a chroma-desktop binary on your PATH for develop mode. End users can install Desktop from the installers in the Desktop guide without the full CLI.

Same API, different internals

Use this mental model when reading plugin and storage docs:

ConcernPortable (Web)Executable (Desktop)
loadPlugins()Runs in the pageRuns in the iframe; same loader contract
@chroma/styleinjectStyles + bootstrapStyle in the page; CSS via <link> or plugin URLsSame CSS files in dist/plugins/chroma-style/; loopback origin only
@chroma/fs writeFileOften IndexedDBOften filesystem bridge when backend: "filesystem"
chroma dev reloadBrowser reloads on saveDevelop window does not; use --no-desktop for fast UI iteration
Screen router (createRouter in WASM)Hash/history in the tabSame in the iframe
API routerSimulated endpoints in-pageSame simulation; still no real server in production unless you add one

If a feature "works in Desktop," confirm it in a browser build (or the opposite) before release. Document mode-specific persistence in your own app, since storage differs between the two modes.

Choosing a mode

GoalRecommended mode
Public website, SSO, SEO, widest reachPortable (Web)
Internal tool, offline zip, consistent file paths on diskExecutable (Desktop)
Fastest edit–refresh loop during UI workPortable dev (--no-desktop) even if you ship Desktop
One codebase, both targetsSingle chroma build; test Portable hosting and Desktop import

Set dev.desktop in chroma.json to match how you prefer to develop; it does not change the production artifact. See dev.desktop in the manifest reference.

Experimental mobile shells wrap the same Portable build; they are not production-ready. See Desktop — experimental mobile and Limitations.

On this page