← 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