← ConceptsOne Layout JSON per screenThe same file drives SwiftUI, Jetpack Compose, and React. Not a lowest-common-denominator intermediate format — the generator emits each platform's idiomatic code. Here is the thinking behind that choice.~5 min read
One source of truthA screen's visual structure is authored once. The Layout JSON lists the type tree, the bindings, the style references — nothing else. No per-platform file forks. When a node genuinely must differ per platform, the branch is declared in the JSON itself — a string `platform` filter that drops the node on the other hosts, or a `platform` override map for per-host attribute values — never in Swift-vs-Compose code forks. Viewport differences likewise stay declarative: the `responsive` block or ViewModel-driven visibility flags.
hello-world.json
// docs/screens/layouts/hello-world.json{ "type": "View", "orientation": "vertical", "child": [ { "type": "Label", "text": "Hello, JsonUI!", "fontSize": 28 }, { "type": "Button", "text": "Tap me", "onClick": "@{onTap}" } ]}Three platform renderingsThe generator reads the Layout JSON and emits native code per platform. Each output is valid, idiomatic, and stand-alone — there is no runtime interpreter shipped with your app. The JSON is build-time artifact only.
HelloWorld.swift (SwiftUI)
VStack(alignment: .leading) { Text("Hello, JsonUI!").font(.system(size: 28)) Button("Tap me") { viewModel.onTap() }}HelloWorld.kt (Compose)
Column { Text(text = "Hello, JsonUI!", fontSize = 28.sp) Button(onClick = { viewModel.onTap() }) { Text("Tap me") }}HelloWorld.tsx (React)
<div className="flex flex-col"> <span className="text-[28px]">Hello, JsonUI!</span> <button onClick={data.onTap}>Tap me</button></div>Normalization levels (L0 / L1 / L2)The one Layout JSON exists at three normalization levels. L0 is the raw source you edit under `docs/screens/layouts/*.json` — this is the only surface authors touch. L1 is what `jui build` distributes to each platform: aliases are rewritten to canonical attribute names, a `$jui: { normalized: "L1", schemaVersion: 1 }` marker is stamped at the top of the file, and every platform's codegen consumes this uniform shape. Since 2026-07, L1 is default-on — the `normalizeLayouts` build flag is opt-out via `jui.config.json`'s `"normalizeLayouts": false`, not opt-in. L2 is the runtime shape used by Dynamic mode and the hot-reload path — L1 with style merge, `include` expansion, and per-platform filtering applied. Authors only ever edit L0; L1 and L2 are auto-generated and carry `@generated` markers so hand-edits are rejected at build time. The upshot: one write in L0 becomes three synchronized deliveries downstream, and the attribute names in error messages, MCP tools, and generated code all agree because the alias table lives in one place.
src/Layouts/hello-world.json (distributed L1)
// distributed L1 layout (auto-generated by jui build){ "$jui": { "normalized": "L1", "schemaVersion": 1 }, "type": "View", "orientation": "vertical", "child": [ /* ... */ ]}The constraints this imposesCertain patterns that feel natural in a single-platform codebase are forbidden: mid-layout business logic and ad-hoc style inline strings. Platform differences themselves are fine — but only declaratively: a string `platform` filter omits a node on the other hosts, a `platform` override map swaps attribute values per host, and a root-level `platforms` array limits the whole screen. What you cannot write is imperative per-platform code inside the layout. The Layout JSON's grammar is deliberately small so the cross-platform renderers can agree. That means you express advanced cases via custom components (spec-first) rather than by stretching the core grammar.
The compounding winsOn week one, One Layout JSON is a constraint. On month six, it is compounding leverage: a visual fix to the hero, a new state added to a form, a renamed label — each is one edit that lands on three platforms. The spec + Layout + ViewModel triple stays in sync because the tooling refuses to let them drift, and the cost of cross-platform parity approaches zero.
Keep reading
Why spec-firstThe contract that makes one-layout-json stable over years./concepts/why-spec-first
ViewModel-owned stateThe Layout has no state; all mutations flow through the ViewModel./concepts/viewmodel-owned-state