Kimo
Docs
v1.0
Changeloggetkimo.design
Docs
v1.0
File format spec
§5

Nodes

On this page6 sections
  1. 5.1 Tree model
  2. 5.2 Placement fields (every node, including instances)
  3. 5.3 Base fields (every node except instances)
  4. 5.4 Graphics fields
  5. 5.5 Node types
  6. 5.6 Pages
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.

  1. parent is a node id or null (a root node). It is always an id in nodes, never a path.
  2. Only frame and group nodes have children, plus instances through at (§9.4). A node whose parent has another type is invalid.
  3. Sibling order is normatively (order, then id), both compared by UTF-16 code unit (not locale-aware). Siblings are the nodes with the same parent and the same at (absent counts as its own group). Equal order keys are legal (they arise from concurrent inserts) and are linted.
  4. 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.
  5. 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)

FieldTypeDefaultMeaning
typenode type— (required)One of §5.5.
parentId | null— (required)§5.1.
atIdPathabsentOnly when parent is an instance: the descendant of that instance this node attaches to (§9.4).
orderOrder key— (required)§5.1.
namestringabsentHuman-readable layer name. Never used for referencing.
x, ynumberabsent = 0Position 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.
themerecord axis key → valueabsentPer-axis theme pins for this subtree (§10.3).
lockedbooleanfalseEditor state: not selectable on canvas.
metarecord Meta key → anyabsentNamespaced extension data. Readers MUST preserve it and MUST NOT interpret keys of namespaces they do not own. Bindings inside meta are not bindings.
pageIdabsentRoot nodes only: the page the node is on (§5.6). Absent = the first page. Not allowed in instance props or set overrides.
type
Type node type
Default — (required)
Meaning One of §5.5.
parent
Type Id | null
Default — (required)
Meaning §5.1.
at
Type IdPath
Default absent
Meaning Only when parent is an instance: the descendant of that instance this node attaches to (§9.4).
order
Type Order key
Default — (required)
Meaning §5.1.
name
Type string
Default absent
Meaning Human-readable layer name. Never used for referencing.
x, y
Type 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).
position
Type "auto" | "absolute"
Default "auto"
Meaning "absolute" takes the node out of the parent's flex flow.
theme
Type record axis key → value
Default absent
Meaning Per-axis theme pins for this subtree (§10.3).
locked
Type boolean
Default false
Meaning Editor state: not selectable on canvas.
meta
Type 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.
page
Type 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)

FieldTypeDefaultMeaning
rotationnumber0Degrees, counter-clockwise, around the node's top-left corner.
opacityNum in [0,1]1
visibleboolean | ReftrueHidden nodes take no space in layout. Also how a descendant is "removed" inside an instance (set: {visible: false}).
flipX, flipYbooleanfalseMirror around the node's center.
blendLayerBlendMode"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.
componentboolean | ComponentDeffalseMakes the node a component; the object form declares a prop API (§9.6).
placeholdertrue | { by?: string, since?: string }absentIn-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.
rotation
Type number
Default 0
Meaning Degrees, counter-clockwise, around the node's top-left corner.
opacity
Type Num in [0,1]
Default 1
Meaning
visible
Type boolean | Ref
Default true
Meaning Hidden nodes take no space in layout. Also how a descendant is "removed" inside an instance (set: {visible: false}).
flipX, flipY
Type boolean
Default false
Meaning Mirror around the node's center.
blend
Type 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.
component
Type boolean | ComponentDef
Default false
Meaning Makes the node a component; the object form declares a prop API (§9.6).
placeholder
Type 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

FieldTypeDefaultMeaning
fillPaint | Paint[]absent = no fill (text: §8.3)A list paints in order, last on top. [] means explicitly no paint.
strokeStrokeabsent§7.2.
effectsEffect[] | RefabsentA Ref binds a shadow variable (feature tokens/composite).
fill
Type Paint | Paint[]
Default absent = no fill (text: §8.3)
Meaning A list paints in order, last on top. [] means explicitly no paint.
stroke
Type Stroke
Default absent
Meaning §7.2.
effects
Type 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.

typeFields (besides Placement/Base)Notes
frameGraphics; 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).
groupeffectsContainer without size, paint or layout; its bounds are its children's.
rectGraphics; w req, h req: Size; radius: Radius, default 0Images are image paints on rects or frames; there is no image node.
ellipseGraphics; 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 360Arcs and rings.
polygonGraphics; w req, h req; sides req: integer ≥ 3; radius: Num, default 0; star: { ratio?: (0,1] }, ratio default 0.38Regular 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.
pathGraphics; 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.
textGraphics; w, h: Size; content req: string | Ref | TextSpan[]; grow: "auto"|"fixed-width"|"fixed", default "auto"; style: TextStyle§8.
iconw req, h req; set req: string; icon req: string | Ref (glyph name); weight: Num 100–700; strokeWidth: Num > 0; color: Color; effectsKnown 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.
notew, 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.
promptas note, plus model: ModelRef | string; target: IdA 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.
scriptw req, h req; src req: relative path to a .js file; inputs: record name → any (literals or Refs); clip: boolean | Ref, default falseFeature node/script. Generated children are derived state and are not stored. The script protocol is out of scope for 0.1.
browserw req, h req; url req: string; device: string; zoom: > 0, default 1; scrollX, scrollY: default 0; radius, default 0; stroke; effectsFeature node/browser. A live web page on the canvas.
instanceof req: QId; props: struct; args: record; overrides: record IdPath → Override; component: boolean | ComponentDef, default false§9. Has no Base fields at its top level.
frame
Fields (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).
group
Fields (besides Placement/Base) effects
Notes Container without size, paint or layout; its bounds are its children's.
rect
Fields (besides Placement/Base) Graphics; w req, h req: Size; radius: Radius, default 0
Notes Images are image paints on rects or frames; there is no image node.
ellipse
Fields (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 360
Notes Arcs and rings.
polygon
Fields (besides Placement/Base) Graphics; w req, h req; sides req: integer ≥ 3; radius: Num, default 0; star: { ratio?: (0,1] }, ratio default 0.38
Notes 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
Fields (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.
text
Fields (besides Placement/Base) Graphics; w, h: Size; content req: string | Ref | TextSpan[]; grow: "auto"|"fixed-width"|"fixed", default "auto"; style: TextStyle
Notes §8.
icon
Fields (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; effects
Notes 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
Fields (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.
prompt
Fields (besides Placement/Base) as note, plus model: ModelRef | string; target: Id
Notes 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
Fields (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 false
Notes Feature node/script. Generated children are derived state and are not stored. The script protocol is out of scope for 0.1.
browser
Fields (besides Placement/Base) w req, h req; url req: string; device: string; zoom: > 0, default 1; scrollX, scrollY: default 0; radius, default 0; stroke; effects
Notes Feature node/browser. A live web page on the canvas.
instance
Fields (besides Placement/Base) of req: QId; props: struct; args: record; overrides: record IdPath → Override; component: boolean | ComponentDef, default false
Notes §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.

  1. The root member pages maps 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.
  2. A root node (parent: null) is on the page its page names. A root without page, or whose page names no entry of pages (lint PAGE_UNKNOWN), is on the first page. So a document without pages (or with pages: {}) has exactly one implicit page holding every root; editors name it "Page 1".
  3. Every other node is on its root's page. page on a non-root node is ignored (lint PAGE_NOT_ROOT); writers moving a root into a container SHOULD remove it, and writers moving a node to the root SHOULD set it.
  4. 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.
  5. Viewport, selection and the active page are editor state, not document data.
  6. 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.
  7. Live sync merges pages per page id (like imports); git three-way merges merge it as a record (§21: a header conflict may address pages).