JsonUI
← Split overviewSeparating the layout filePattern 1 of spec splitting, in detail. Move the visual tree out of the spec into a sibling Layout JSON, point `metadata.layoutFile` at it, and let `layout_importer` stitch the two back together at generate time. This is the most common split pattern on the site and the easiest starting point.~8 min read
Why separate the layoutA spec file mixes three different kinds of content — the contract (uiVariables, eventHandlers), the data flow (customTypes, viewModel), and the visual tree (structure.layout). Each kind has a different reviewer: designers for the tree, platform engineers for the VM, product for the copy. Keeping them in one file forces every change to go through one merge queue and one PR; splitting the layout into its own file lets the designer iterate on the tree without touching the contract.
Declaring itSet `metadata.layoutFile` to a path relative to `docs/screens/layouts/` (the `.json` extension is optional). Leave `structure.components` and `structure.layout` empty — the validator does not require them when a `layoutFile` is set, and the importer will populate them from the referenced file. The pair below is a realistic minimum.
spec + Layout JSON pair
// Side A — the spec. `structure` stays mostly empty; layout_importer
// inlines the tree at generate time.
{
"type": "screen_spec",
"version": "1.0",
"metadata": {
"name": "RecentActivity",
"layoutFile": "recent-activity" // <- resolves to
// docs/screens/layouts/recent-activity.json
// (extension optional, nested paths allowed)
},
"structure": {
"components": [], // auto-filled by layout_importer
"layout": {} // same
}
}
 
// Side B — the Layout JSON at docs/screens/layouts/recent-activity.json.
// Owns the hierarchy; the spec doesn't need to restate it.
{
"type": "View",
"orientation": "vertical",
"child": [
{ "type": "Label", "text": "@{headline}", "fontSize": 28, "fontWeight": "bold" },
{ "type": "Button", "text": "@{ctaLabel}", "onClick": "@{onTapCta}" }
]
}
How paths resolveThe importer joins `layouts_directory` (from `jui.config.json`, default `docs/screens/layouts`) with the `layoutFile` string. Nested paths work — this site uses `learn/hello-world`, `concepts/data-binding`, `spec/split-overview`, and so on. Missing files fail at generate time, not build time, so `jui verify --fail-on-diff` catches typos in CI.
layoutFile resolution
layoutFile string → resolved path
------------------------------------- ---------------------------------------
"recent-activity" → docs/screens/layouts/recent-activity.json
"learn/hello-world" → docs/screens/layouts/learn/hello-world.json
"spec/split-overview" → docs/screens/layouts/spec/split-overview.json
"concepts/why-spec-first.json" → docs/screens/layouts/concepts/why-spec-first.json
(trailing .json is optional; either form works)
What layout_importer does`layout_importer` is a generate-time pass inside the jsonui-doc CLI. It reads the Layout JSON, walks the tree, and rewrites the spec in memory so every downstream generator sees a fully-populated `structure`. The *file on disk* is not mutated — the split stays clean, the enriched spec exists only for the duration of the render.
layout_importer flow
// What layout_importer (jsonui-cli/document_tools) does at generate time:
//
// 1. Read spec_data.metadata.layoutFile → "learn/hello-world"
// 2. Read docs/screens/layouts/learn/hello-world.json → the Layout tree
// 3. Walk the tree; collect every View / custom component → structure.components[]
// 4. Inline the full tree under → structure.layout
// 5. Harvest every binding (@{...}) referenced in the tree → stateManagement.uiVariables
// (skipped when the spec already declares that variable by name)
// 6. Mark the spec with metadata._layoutFileImported = true so repeat imports are safe
//
// After step 6, generate_spec_html / generate_spec_markdown render from the
// enriched spec; the standalone spec file on disk is never mutated.
Sharing one Layout across specsTwo specs can point at the same `layoutFile`. Use this for language editions (en / ja dashboards rendering the same tree with different seeded strings), for A/B copy experiments, or for temporary dual-specs during a rename. Each spec still has its own contract; only the visual tree is shared.
two specs, one Layout
// home-en.spec.json — English edition, English copy, English-dated headline.
{
"metadata": {
"name": "HomeEn",
"displayName": "Home (English)",
"layoutFile": "home-dashboard" // shared Layout
}
}
 
// home-ja.spec.json — Japanese edition, same dashboard, different seeded strings.
{
"metadata": {
"name": "HomeJa",
"displayName": "Home (日本語)",
"layoutFile": "home-dashboard" // the SAME Layout
}
}
Directory conventionsSpecs live under `docs/screens/json/`, Layouts under `docs/screens/layouts/`, styles under `docs/screens/styles/`, reusable cells under `docs/screens/layouts/cells/`, and component specs (Pattern 3) under `docs/components/json/`. `jui init` seeds these directories on first run; `jui.config.json` lets you override any of them if your repo has a different convention.
on-disk layout
docs/
├── screens/
│ ├── json/ ← specs (authored)
│ │ ├── learn/hello-world.spec.json
│ │ └── spec/layout-file.spec.json
│ ├── layouts/ ← layouts (authored, referenced via metadata.layoutFile)
│ │ ├── learn/hello-world.json
│ │ ├── spec/layout-file.json
│ │ └── cells/ ← Pattern 5 (cellClasses) lives here
│ ├── styles/ ← shared style files
│ └── layouts/Resources/
│ └── strings.json ← every @string/... key
└── components/
└── json/ ← component_spec.json files (Pattern 3)
Keep goingOne pattern down, four to go. Head back to the overview for the map, or dive into the next detail article.
Six ways to split a specThe map article. Jump back for the other four patterns and the picker cheat sheet./spec/split-overview
Parent + sub specsPattern 2 — split one screen into multiple spec files by region of state./spec/parent-sub-spec