Targ Apps Docs
Chroma

.chr source format

What a .chr file is, how to name and import them, and how the dev server and build treat them.

A .chr file is a JavaScript ES module with a Chroma-specific extension. Application UI and logic live in src/**/*.chr; configuration stays in JSON (chroma.json, chroma-fs.json, plugin manifests). There is no separate .chr compiler: the dev server serves the files as JavaScript and the build bundles them with esbuild. Editors and the linter use the extension to recognize Chroma code (language id chroma).

Naming and imports

RuleExample
Extension is .chr, one module per fileDashboard.chr
PascalCase for screens and components, main for the entrysrc/main.chr, src/screens/Settings.chr
No .js or .ts for app logic under src/CSS and images are fine
Imports include the extensionimport { App } from "./App.chr"

Leaving off the extension (from "./App") fails in the browser, which resolves modules without guessing extensions. Engine imports use the relative path to pkg/: ../pkg/chroma.js from src/, ../../pkg/chroma.js from src/components/.

Folder layout

my-app/
├── chroma.json
├── index.html
├── src/
│   ├── main.chr            # bootstrap: renders the root component
│   ├── App.chr             # root component
│   ├── screens/            # one file per screen
│   │   ├── Dashboard.chr
│   │   └── Settings.chr
│   ├── components/
│   │   └── Button.chr
│   └── lib/
│       └── format.chr      # shared helpers
└── plugins/

chroma new creates main.chr, App.chr, and components/; screens/ and lib/ are conventions you add when the app grows.

A multi-file example

// src/lib/format.chr
export function formatPrice(cents) {
    return (cents / 100).toFixed(2);
}
// src/screens/Catalog.chr
import { e } from "../../pkg/chroma.js";
import { formatPrice } from "../lib/format.chr";

export function Catalog() {
    return e("p", null, "Total: $", formatPrice(1250));
}
// src/App.chr
import { e } from "../pkg/chroma.js";
import { Catalog } from "./screens/Catalog.chr";

export function App() {
    return e("main", null, e(Catalog));
}
// src/main.chr
import { render } from "../pkg/chroma.js";
import { App } from "./App.chr";

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

index.html loads the entry with <script type="module" src="./src/main.chr"></script>. To switch between screens, use the screen router.

How the toolchain treats .chr

StageBehavior
chroma devThe dev server answers .chr with Content-Type: text/javascript, so the browser loads it as a module.
chroma build (dynamic)esbuild runs with --loader:.chr=js and bundles everything reachable from build.jsEntry into one file.
chroma validateAccepts a .chr build.jsEntry and warns about leftover .js files under src/. Warnings do not fail the command.
chroma-lint / VS Code.chr mode turns on automatically when build.jsEntry ends in .chr or any .chr file exists under src/. Then .js, .jsx, .mjs, .cjs, .ts, and .tsx files under src/ are errors, and build.jsEntry must end in .chr. Details and the opt-in keys: Linter.

Set build.jsEntry in chroma.json to your entry module (src/main.chr in the templates). Treat dist/ and bundle.js as build output, not source.

Migrate from .js

  1. Rename src/**/*.js to .chr.
  2. Add .chr to every import of a renamed file.
  3. Point build.jsEntry at src/main.chr and the <script> in index.html at ./src/main.chr.
  4. Run chroma validate, then chroma dev.

On this page