Components, instances, overrides and slots
On this page8 sections
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).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).@kimo/core-schema (packages/core-schema). Where this document and the implementation disagree, that is a bug in one of them; file an issue.docs/teardown.md §10. Every place where this spec resolves an ambiguity in, or deviates from, that section is listed in §20.9.1 Components
Any non-instance node with a truthy component is a component; its subtree (every node whose parent chain reaches it) is the component's content. An instance MAY also carry component — a derived component (e.g. a variant: Button/Ghost = an instance of Button/Primary with overrides, marked as a component). Paths into a derived component resolve through to the component it instantiates (§9.5).
A component MUST NOT contain, directly or through nested instances or derived components, an instance of itself (INSTANCE_CYCLE). This includes what an instance places inside its component: a set.of swap of a nested instance and an args value of an instance prop put the named component inside every component that holds the target (the instance's component and the component of each instance the path crosses), so naming one of those is a cycle too.
9.2 Instances
ofnames a component: a local id, oralias:idfor a library component. The target MUST exist and MUST be a component.- An instance has only Placement fields at its top level (plus
of,props,args,overrides,component). Every visual property of the component root (w,h,fill,opacity,visible,rotation,flipX,layout, …) MAY appear only insideprops, never at the instance's top level (INSTANCE_VISUAL_FIELD).propsMUST NOT contain Placement fields orcomponent; applyingpropsto the root MUST yield a valid node. argsholds values for the component's declared props (§9.6).- An instance has no children of its own: its subtree is the component's, plus instance-owned nodes (§9.4).
9.3 Overrides
overrides maps an IdPath (§9.5) to exactly one of:
| Override | Meaning |
|---|---|
{ "set": { … } } | Property override. Each key replaces the target's property wholesale (shallow: set: {layout: …} replaces the whole layout). Keys type, parent, at, order, component and overrides MUST NOT appear. Applying set to the target MUST yield a valid node of the target's type. When the target is an instance, set uses instance fields (of swaps the component, props, args, theme, name, x, y, position); visual fields go inside set.props. |
{ "slot": true } | Children replacement. The target MUST be a frame with slot. Its children become exactly the nodes with parent = this instance and at = this key, in sibling order. Zero nodes = an empty slot. |
{ "replace": true } | Full replacement. Exactly one node with parent = this instance and at = this key replaces the target (and its subtree). |
{ "set": { … } }set: {layout: …} replaces the whole layout). Keys type, parent, at, order, component and overrides MUST NOT appear. Applying set to the target MUST yield a valid node of the target's type. When the target is an instance, set uses instance fields (of swaps the component, props, args, theme, name, x, y, position); visual fields go inside set.props.{ "slot": true }slot. Its children become exactly the nodes with parent = this instance and at = this key, in sibling order. Zero nodes = an empty slot.{ "replace": true }parent = this instance and at = this key replaces the target (and its subtree).Overrides are explicitly tagged; their meaning never depends on which keys a value happens to contain.
Precedence (later wins): the component as authored → a derived component's own props/args/overrides → the instance's props (root) → the instance's args (declared props) → the instance's overrides. For nested instances, an outer instance's override of a deep path wins over the nested instance's own overrides. Overrides do not cascade to other instances of the same component.
To hide a descendant inside an instance, set visible: false; descendants of an instance cannot be deleted.
9.4 Instance-owned nodes (at)
Slot content and replacement nodes live in the flat node table like any other node, so they diff, merge and sync the same way:
parent= the instance id (a real Id),at= the IdPath of the descendant they attach to.- A node whose parent is an instance MUST have
at, andatMUST equal a key of that instance'soverrideswhose value isslotorreplace(AT_WITHOUT_OVERRIDE). A node whose parent is not an instance MUST NOT haveat. - The override entry is only a marker: the child list's single source of truth is the set of nodes pointing at it.
9.5 Id path resolution
An IdPath is resolved relative to a component C (for an override or at: the instance's component; for a declared prop target: the declaring component):
- Split the path at
/. For each segment, in order:- The segment is looked up among the descendants of C (nodes whose parent chain reaches C, including instance-owned nodes inside C), in C's namespace: written as a bare Id when C is a node of this document, and as
alias:Idwhen C belongs to the library imported asalias. - If not found and C is a derived component (an instance), C is replaced by the component it instantiates and the lookup is repeated (derived components are transparent).
- If it is not the last segment, the found node MUST be an instance, and C becomes its component; the next segment crosses that instance boundary.
- The segment is looked up among the descendants of C (nodes whose parent chain reaches C, including instance-owned nodes inside C), in C's namespace: written as a bare Id when C is a node of this document, and as
- The node found for the last segment is the target. Any failure is
OVERRIDE_PATH_UNRESOLVED.
So "ui:CardActns1/ crosses from a library card into a library button. Paths never use names, so renames cannot break them. In 1.0, a path segment that would enter a library imported by a library (rather than by this document) is not resolved by validators (reported as not checked).
9.6 Declared component props
component: { "props": { <name>: PropDef } } declares a typed prop API (used by the React exporter: typed props for declared props only; every other override is inlined).
PropDef type | Fields | Target requirement | An args value… |
|---|---|---|---|
"text" | target, field: "content", default?: string | a text node | string or string binding; sets content |
"boolean" | target, field: "visible", default?: boolean | any non-instance node | boolean or boolean binding; sets visible |
"instance" | target, default?: QId | an instance | a component QId; swaps the target's of |
"enum" | options (record option → value, ≥ 1); target + field (together, optional); targets (optional record option → EnumTarget[]) | target: a node that has field (not type/parent/at/order/component/overrides); every option value MUST be valid for field. Without target/field, targets is required and option values are informative (editors write null) | an option name; sets field to that option's value, then applies that option's targets in order |
"slot" | target | a frame with slot | not allowed: slot content is given by nodes with at (§9.4); exporters emit children or a named ReactNode prop |
"text"target, field: "content", default?: stringtext nodecontent"boolean"target, field: "visible", default?: booleanvisible"instance"target, default?: QIdinstanceof"enum"options (record option → value, ≥ 1); target + field (together, optional); targets (optional record option → EnumTarget[])target: a node that has field (not type/parent/at/order/component/overrides); every option value MUST be valid for field. Without target/field, targets is required and option values are informative (editors write null)field to that option's value, then applies that option's targets in order"slot"targetslotat (§9.4); exporters emit children or a named ReactNode proptarget is an IdPath resolved from the declaring component (§9.5). A target equal to the declaring component's own id names the component root (through derived components, the base root); its args apply at the root with the precedence of args (above props). Only declared prop targets may name the root: override keys and at MUST NOT (OVERRIDE_PATH_UNRESOLVED; the root is overridden through props). default is informative for exporters (the generated prop's default); an instance without the arg renders the component as authored; enum props have no default. args keys MUST be declared props (ARG_UNKNOWN).
Enum targets (board 27 §8 a). targets[option] is a list of EnumTarget { target, field, value }: target is an IdPath resolved like a prop target (the declaring component's own id names the root), field is a property name of the target node, or <property>/<key> for one key of a struct property (layout/pad, layout/gap, style/size, style/weight, an instance's props/opacity) — the property keeps its other keys (an absent property starts empty). value MUST be valid for the field (checked as the property with that key replaced) and MAY be a binding. Every targets key MUST be an option (PROP_TARGET_INVALID), as are a target that does not resolve, a field the node lacks, or a target without field (and vice versa). Choosing an option on an instance applies the legacy target/field value first, then the option's targets in list order, all with the precedence of args; args apply in key order. Properties an option does not target keep the main's values. A file whose enum props use only target/field/options is unchanged; editors MAY rewrite such a prop as targets (one target per option) when it is edited.
Every PropDef MAY carry order (an Order key): component.props is a record, so canonical serialization sorts it by name; editors list declared props by (order, then name), an absent order counting as the empty string, and SHOULD write an order on every declaration they create. A derived component's redeclaration without order keeps the base declaration's position.
Derived components inherit the prop API. The declared props of a derived component are those of the component it instantiates (recursively), with its own component.props declarations merged over them by name. Inherited targets resolve through the derived component (§9.5), so an instance of Button/Ghost takes the same args as an instance of Button/Primary.
9.7 Component metadata (namespace kimo.component)
| Node meta key | Value | Meaning |
|---|---|---|
kimo.component.description | string | The component's description (editor Component section; exporters MAY emit it as a doc comment). |
kimo.component.description9.8 Expansion (normative for readers that render or edit instances)
Readers expand an instance to its component's content as follows; @kimo/core-schema createExpandedView is the reference.
- Composite ids. A node emitted from component content is addressed as
<instance id>/<IdPath>, the IdPath being the node's path relative to the instance's component (§9.5) — exactly the key an override of that instance uses. Instance-owned nodes keep their own ids. Real ids never contain/. - Root. The expanded root is the base component root with
props(and root-targetingargs) applied; its placement fields (parent,at,order,x,y,position,locked,meta,namewhen the instance has one) are the instance's. Itsthemeis the component root's pins (base first, then each derived component's own pins) overwritten by the instance's pins, so a component authored under a pinned theme keeps it unless the instance pins otherwise. - Library content is read in the consumer's namespace: bindings, theme pin keys,
ofandslot.suggestedvalues written inside libraryaliasget thealias:prefix. - Theme of expanded nodes. The placement path of an expanded node runs through the expanded tree (instance → component content → slot frame → slot content), so pins inside component content apply to slot content placed in it.
The same rules apply to set.props and set.args of a set override on a nested instance: they are checked against that instance's effective component (the one named by set.of, if present) exactly like an instance's own props and args. Likewise an override key that crosses a nested instance continues in its effective component: when the holder swaps the nested instance (set.of on that path, else an instance prop arg targeting it), the remaining segments resolve in the swapped component, and keys that no longer resolve are OVERRIDE_PATH_UNRESOLVED.
- Precedence of kinds across nesting levels. When an outer instance overrides a deep path with
slotorreplace, it replaces the nested instance's ownslot/replaceon that path; an outerseton a path the nested instance replaced targets a node that is not there and is not applied. - Size. Readers MAY bound the number of nodes one top-level expansion emits (the reference stops at 50 000 and reports
INSTANCE_TOO_LARGE), since depth alone does not bound fan-out.