File format spec
§5
Nodes
On this page6 sections
Status
Stable (format version
1.0.0, frozen 2026-10). Backward compatible within 1.x (§16.4). Supersedes the draft v0.1, kept as a historical record; 0.1 files open through the 0.1 → 1.0 migration (§16.2).Schema
https://getkimo.design/schema/kimo-1.0.json (JSON Schema draft 2020-12), generated into packages/core-schema/schema/kimo-1.0.schema.json . The 0.1 schema stays published at …/kimo-0.1.json (schema/kimo-0.1.schema.json, frozen).Reference
@kimo/core-schema (packages/core-schema). Where this document and the implementation disagree, that is a bug in one of them; file an issue.Plan of record
docs/teardown.md §10. Every place where this spec resolves an ambiguity in, or deviates from, that section is listed in §20.License
MIT
5.1 Tree model
The document is a forest stored as a flat table. Each node names its parent; readers rebuild children lists.
parentis a node id ornull(a root node). It is always an id innodes, never a path.- Only
frameandgroupnodes have children, plus instances throughat(§9.4). A node whose parent has another type is invalid. - Sibling order is normatively (
order, then id), both compared by UTF-16 code unit (not locale-aware). Siblings are the nodes with the sameparentand the sameat(absent counts as its own group). Equalorderkeys are legal (they arise from concurrent inserts) and are linted. - Parent links MUST NOT form a cycle. When a cycle appears only after a CRDT or git merge, the editor repairs it: the node whose move has the later timestamp (ties: larger id) is reparented to its previous parent, or to the root if that is also in the cycle.
- Children paint in sibling order: later siblings on top.
Keywords:
MUST
MUST NOT
SHOULD
MAY
set in small mono caps wherever they are normative
5.2 Placement fields (every node, including instances)
| Field | Type | Default | Meaning |
|---|---|---|---|
type | node type | — (required) | One of §5.5. |
parent | Id | null | — (required) | §5.1. |
at | IdPath | absent | Only when parent is an instance: the descendant of that instance this node attaches to (§9.4). |
order | Order key | — (required) | §5.1. |
name | string | absent | Human-readable layer name. Never used for referencing. |
x, y | number | absent = 0 | Position relative to the parent's top-left, y down. Ignored in a parent with layout unless position is "absolute". Not part of the defaults table: an explicit 0 is kept (§14). |
position | "auto" | "absolute" | "auto" | "absolute" takes the node out of the parent's flex flow. |
theme | record axis key → value | absent | Per-axis theme pins for this subtree (§10.3). |
locked | boolean | false | Editor state: not selectable on canvas. |
meta | record Meta key → any | absent | Namespaced extension data. Readers MUST preserve it and MUST NOT interpret keys of namespaces they do not own. Bindings inside meta are not bindings. |
page | Id | absent | Root nodes only: the page the node is on (§5.6). Absent = the first page. Not allowed in instance props or set overrides. |
typeType node type
Default — (required)
Meaning One of §5.5.
parentType Id | null
Default — (required)
Meaning §5.1.
atType IdPath
Default absent
Meaning Only when
parent is an instance: the descendant of that instance this node attaches to (§9.4).orderType Order key
Default — (required)
Meaning §5.1.
nameType string
Default absent
Meaning Human-readable layer name. Never used for referencing.
x, yType number
Default absent = 0
Meaning Position relative to the parent's top-left, y down. Ignored in a parent with
layout unless position is "absolute". Not part of the defaults table: an explicit 0 is kept (§14).positionType
"auto" | "absolute"Default
"auto"Meaning
"absolute" takes the node out of the parent's flex flow.themeType record axis key → value
Default absent
Meaning Per-axis theme pins for this subtree (§10.3).
lockedType boolean
Default
falseMeaning Editor state: not selectable on canvas.
metaType record Meta key → any
Default absent
Meaning Namespaced extension data. Readers MUST preserve it and MUST NOT interpret keys of namespaces they do not own. Bindings inside
meta are not bindings.pageType Id
Default absent
Meaning Root nodes only: the page the node is on (§5.6). Absent = the first page. Not allowed in instance
props or set overrides.5.3 Base fields (every node except instances)
| Field | Type | Default | Meaning |
|---|---|---|---|
rotation | number | 0 | Degrees, counter-clockwise, around the node's top-left corner. |
opacity | Num in [0,1] | 1 | |
visible | boolean | Ref | true | Hidden nodes take no space in layout. Also how a descendant is "removed" inside an instance (set: {visible: false}). |
flipX, flipY | boolean | false | Mirror around the node's center. |
blend | LayerBlendMode | "normal" | How the node (with its subtree) composites onto what is behind it. On a frame or group, every mode but "pass-through" composites the subtree as an isolated group first, then blends it with that mode; "pass-through" composites no group, so descendants blend directly with the backdrop (CSS: no isolation). On other node types "pass-through" means "normal"; writers SHOULD write "normal" there. The isolated group matters only when a descendant uses a non-normal blend, so exporters MAY omit isolation: isolate otherwise. |
component | boolean | ComponentDef | false | Makes the node a component; the object form declares a prop API (§9.6). |
placeholder | true | { by?: string, since?: string } | absent | In-progress marker (e.g. an agent is working on it; since is an ISO 8601 timestamp). by is the editor-state origin of the owner (agent:kimo.<session>.<n>, mcp:<client>.<session>); an editor whose owner is not running shows the frame as interrupted (ADR 0014). Owners MUST clear the flag when their work ends; it never locks the node for people. |
rotationType number
Default
0Meaning Degrees, counter-clockwise, around the node's top-left corner.
opacityType Num in [0,1]
Default
1Meaning
visibleType boolean | Ref
Default
trueMeaning Hidden nodes take no space in layout. Also how a descendant is "removed" inside an instance (
set: {visible: false}).flipX, flipYType boolean
Default
falseMeaning Mirror around the node's center.
blendType LayerBlendMode
Default
"normal"Meaning How the node (with its subtree) composites onto what is behind it. On a
frame or group, every mode but "pass-through" composites the subtree as an isolated group first, then blends it with that mode; "pass-through" composites no group, so descendants blend directly with the backdrop (CSS: no isolation). On other node types "pass-through" means "normal"; writers SHOULD write "normal" there. The isolated group matters only when a descendant uses a non-normal blend, so exporters MAY omit isolation: isolate otherwise.componentType boolean | ComponentDef
Default
falseMeaning Makes the node a component; the object form declares a prop API (§9.6).
placeholderType
true | { by?: string, since?: string }Default absent
Meaning In-progress marker (e.g. an agent is working on it;
since is an ISO 8601 timestamp). by is the editor-state origin of the owner (agent:kimo.<session>.<n>, mcp:<client>.<session>); an editor whose owner is not running shows the frame as interrupted (ADR 0014). Owners MUST clear the flag when their work ends; it never locks the node for people.5.4 Graphics fields
| Field | Type | Default | Meaning |
|---|---|---|---|
fill | Paint | Paint[] | absent = no fill (text: §8.3) | A list paints in order, last on top. [] means explicitly no paint. |
stroke | Stroke | absent | §7.2. |
effects | Effect[] | Ref | absent | A Ref binds a shadow variable (feature tokens/composite). |
fillType Paint | Paint[]
Default absent = no fill (text: §8.3)
Meaning A list paints in order, last on top.
[] means explicitly no paint.strokeType Stroke
Default absent
Meaning §7.2.
effectsType Effect[] | Ref
Default absent
Meaning A Ref binds a
shadow variable (feature tokens/composite).5.5 Node types
Required fields are marked req. Every type also has the Placement fields; every type but instance has the Base fields.
type | Fields (besides Placement/Base) | Notes |
|---|---|---|
frame | Graphics; w, h: Size, default {mode:"fit"}; radius: Radius, default 0; clip: boolean | Ref, default false; layout: Layout (absent = no flex layout); slot: { suggested?: QId[] } | Container. slot marks the frame as a slot of its component; suggested is advisory (slot content is not restricted to it), but every entry MUST name a component (REF_MISSING, REF_NOT_COMPONENT, ALIAS_UNKNOWN). |
group | effects | Container without size, paint or layout; its bounds are its children's. |
rect | Graphics; w req, h req: Size; radius: Radius, default 0 | Images are image paints on rects or frames; there is no image node. |
ellipse | Graphics; w req, h req; innerRadius: [0,1), default 0 (ring hole as a fraction); startAngle: degrees CCW from the positive x axis, default 0; sweepAngle: [-360,360], default 360 | Arcs and rings. |
polygon | Graphics; w req, h req; sides req: integer ≥ 3; radius: Num, default 0; star: { ratio?: (0,1] }, ratio default 0.38 | Regular polygon inscribed in the box. With star it is a star with sides points: the outer vertices are the polygon's, and an inner vertex sits between each pair, on the same ellipse scaled by ratio (inner / outer radius). radius rounds every vertex. |
path | Graphics; w, h: Size (absent = the viewBox size, else the geometry's bounds); d req: SVG path data; fillRule: "nonzero"|"evenodd", default "nonzero"; viewBox: [x, y, w, h] (absent = the geometry's bounding box) | The geometry is scaled from viewBox to w×h. Writers SHOULD set viewBox. |
text | Graphics; w, h: Size; content req: string | Ref | TextSpan[]; grow: "auto"|"fixed-width"|"fixed", default "auto"; style: TextStyle | §8. |
icon | w req, h req; set req: string; icon req: string | Ref (glyph name); weight: Num 100–700; strokeWidth: Num > 0; color: Color; effects | Known sets: lucide, feather, phosphor, material-outlined, material-rounded, material-sharp. Other values are allowed and render as a missing glyph. Icons are rendered as paths from the set, scaled from the set's grid to w×h. weight applies to Material sets (the wght axis; absent = 400) and to phosphor (nearest of thin 100, light 300, regular 400, bold 700; absent = regular); other sets ignore it. strokeWidth applies to stroke sets (lucide, feather) in the set's 24-unit grid units (absent = the set's own width, 2); other sets ignore it. |
note | w, h: Size (absent = fit content); content req: string; style: TextStyle; author: string (display name) | Sticky note. Not part of the design: exporters (code, SVG, PNG/PDF of a parent) and Play skip notes and prompts. Agents read notes as context and MUST NOT edit a person's note. |
prompt | as note, plus model: ModelRef | string; target: Id | A runnable AI prompt on the canvas (ADR 0014). model is { provider, model } (both non-empty strings); a bare string is the legacy form and MUST resolve to exactly one configured provider or the run fails (never a silent fallback). target names the frame a run is scoped to; absent, a run creates a new frame next to the prompt. A target that is not a frame of the document is linted (PROMPT_TARGET_MISSING). Run state (last run, cost) is app data, not stored in the file. |
script | w req, h req; src req: relative path to a .js file; inputs: record name → any (literals or Refs); clip: boolean | Ref, default false | Feature node/script. Generated children are derived state and are not stored. The script protocol is out of scope for 0.1. |
browser | w req, h req; url req: string; device: string; zoom: > 0, default 1; scrollX, scrollY: default 0; radius, default 0; stroke; effects | Feature node/browser. A live web page on the canvas. |
instance | of req: QId; props: struct; args: record; overrides: record IdPath → Override; component: boolean | ComponentDef, default false | §9. Has no Base fields at its top level. |
frameFields (besides Placement/Base) Graphics;
w, h: Size, default {mode:"fit"}; radius: Radius, default 0; clip: boolean | Ref, default false; layout: Layout (absent = no flex layout); slot: { suggested?: QId[] }Notes Container.
slot marks the frame as a slot of its component; suggested is advisory (slot content is not restricted to it), but every entry MUST name a component (REF_MISSING, REF_NOT_COMPONENT, ALIAS_UNKNOWN).groupFields (besides Placement/Base)
effectsNotes Container without size, paint or layout; its bounds are its children's.
rectFields (besides Placement/Base) Graphics;
w req, h req: Size; radius: Radius, default 0Notes Images are image paints on rects or frames; there is no image node.
ellipseFields (besides Placement/Base) Graphics;
w req, h req; innerRadius: [0,1), default 0 (ring hole as a fraction); startAngle: degrees CCW from the positive x axis, default 0; sweepAngle: [-360,360], default 360Notes Arcs and rings.
polygonFields (besides Placement/Base) Graphics;
w req, h req; sides req: integer ≥ 3; radius: Num, default 0; star: { ratio?: (0,1] }, ratio default 0.38Notes Regular polygon inscribed in the box. With
star it is a star with sides points: the outer vertices are the polygon's, and an inner vertex sits between each pair, on the same ellipse scaled by ratio (inner / outer radius). radius rounds every vertex.pathFields (besides Placement/Base) Graphics;
w, h: Size (absent = the viewBox size, else the geometry's bounds); d req: SVG path data; fillRule: "nonzero"|"evenodd", default "nonzero"; viewBox: [x, y, w, h] (absent = the geometry's bounding box)Notes The geometry is scaled from
viewBox to w×h. Writers SHOULD set viewBox.textFields (besides Placement/Base) Graphics;
w, h: Size; content req: string | Ref | TextSpan[]; grow: "auto"|"fixed-width"|"fixed", default "auto"; style: TextStyleNotes §8.
iconFields (besides Placement/Base)
w req, h req; set req: string; icon req: string | Ref (glyph name); weight: Num 100–700; strokeWidth: Num > 0; color: Color; effectsNotes Known sets:
lucide, feather, phosphor, material-outlined, material-rounded, material-sharp. Other values are allowed and render as a missing glyph. Icons are rendered as paths from the set, scaled from the set's grid to w×h. weight applies to Material sets (the wght axis; absent = 400) and to phosphor (nearest of thin 100, light 300, regular 400, bold 700; absent = regular); other sets ignore it. strokeWidth applies to stroke sets (lucide, feather) in the set's 24-unit grid units (absent = the set's own width, 2); other sets ignore it.noteFields (besides Placement/Base)
w, h: Size (absent = fit content); content req: string; style: TextStyle; author: string (display name)Notes Sticky note. Not part of the design: exporters (code, SVG, PNG/PDF of a parent) and Play skip notes and prompts. Agents read notes as context and MUST NOT edit a person's note.
promptFields (besides Placement/Base) as
note, plus model: ModelRef | string; target: IdNotes A runnable AI prompt on the canvas (ADR 0014).
model is { provider, model } (both non-empty strings); a bare string is the legacy form and MUST resolve to exactly one configured provider or the run fails (never a silent fallback). target names the frame a run is scoped to; absent, a run creates a new frame next to the prompt. A target that is not a frame of the document is linted (PROMPT_TARGET_MISSING). Run state (last run, cost) is app data, not stored in the file.scriptFields (besides Placement/Base)
w req, h req; src req: relative path to a .js file; inputs: record name → any (literals or Refs); clip: boolean | Ref, default falseNotes Feature
node/script. Generated children are derived state and are not stored. The script protocol is out of scope for 0.1.browserFields (besides Placement/Base)
w req, h req; url req: string; device: string; zoom: > 0, default 1; scrollX, scrollY: default 0; radius, default 0; stroke; effectsNotes Feature
node/browser. A live web page on the canvas.instanceFields (besides Placement/Base)
of req: QId; props: struct; args: record; overrides: record IdPath → Override; component: boolean | ComponentDef, default falseNotes §9. Has no Base fields at its top level.
Any other type is invalid in 1.0 (UNKNOWN_NODE_TYPE) and MUST be preserved by readers.
5.6 Pages
A document is split into pages: independent canvases that partition the root nodes. A page only groups roots; it is not a node and never appears in parent.
- The root member
pagesmaps a page id (Id grammar) to{ "order": Order key, "name": string }. Pages list by (order, then id), code-unit order, like siblings (§5.1). Names need not be unique. Page ids are their own namespace, but writers SHOULD NOT reuse a node id. - A root node (
parent: null) is on the page itspagenames. A root withoutpage, or whosepagenames no entry ofpages(lintPAGE_UNKNOWN), is on the first page. So a document withoutpages(or withpages: {}) has exactly one implicit page holding every root; editors name it "Page 1". - Every other node is on its root's page.
pageon a non-root node is ignored (lintPAGE_NOT_ROOT); writers moving a root into a container SHOULD remove it, and writers moving a node to the root SHOULD set it. - Writers that add the first explicit page to a document SHOULD also give an explicit entry to the implicit page, so the existing roots stay on it whatever the new page's order. Deleting a page deletes its roots (or moves them first); readers never delete nodes because of
pages. - Viewport, selection and the active page are editor state, not document data.
- Exporters and tools that address "the canvas" address one page; whole-document operations (validation, variables, components, assets) span every page. A component on one page may be instantiated on any page.
- Live sync merges
pagesper page id (likeimports); git three-way merges merge it as a record (§21: aheaderconflict may addresspages).