CodelabDocs
Model

Element

A node in a page or component tree — Primitive or Component kind, with props, styles, and optional repeat.

An Element is one node in a page or component tree. Kind + props (attributes) + styles, optional repeat and renderIf. It belongs to exactly one container — a page or a component, never both, never the app directly.

The canvas is this tree. There is no parallel source of truth. A page tree is rooted at body.

Section / h1 / Button tree with purpose aria-labels in the explorer and inspector
Section / h1 / Button tree with purpose aria-labels in the explorer and inspector

Kind

KindPass to toolsMeaning
Primitive{ _tag: "Primitive", value: "section" }A registry widget: HTML, coss, image, …
Component{ _tag: "Component", value: "<componentId>" }An instance of a user component. list_components first.

Known primitive names must use _tag: "Primitive". You cannot pass "Button" as a Component.

Fields

NameTypeDefaultDescription
propsattributesKind-specific and custom values: children, href, className, instance fields, event prop slots. A slot is a literal or a {{…}} expression. Event slots store one function binding (functionId + instanceParams).
stylesstyle stateResponsive style. MCP writes these with style_*, not add_element. On insert, bulk Tailwind goes in className.
repeatablebooleanfalseWhen true, the node repeats once per item of repeatSource. Inside the subtree: {{repeat.item}}, {{repeat.index}}, {{repeat.key}}. You author one node; render expands copies.
repeatSource{{…}}A complete expression that resolves to an array.
renderIf{{…}}alwaysBoolean expression. Falsy → this node and its subtree are omitted.
parentIdstringTree parent. You almost never set this raw — use parentElementId + targetPosition on add/move.
siblingPositionnumberOrder among siblings. Same: set via targetPosition.

The display name in outlines and search is aria-label → component name → kind value.

Tree mutations take a container:

ContainerPass
Page{ _tag: "Page", id: pageId }
Component{ _tag: "Component", id: componentId }

How it relates

  • Page / component — the two containers. add_element with no parentElementId appends under that container's root (body on a page).
  • Primitive vs Component kind — a primitive is a registry widget (div, Button, Dialog). A Component kind is an instance of a user component; its props are that instance's values.
  • Props / Function — settable keys are prop slots. Event slots store one function binding; sequences live on a store Action.
  • App — theme tokens and kind styles paint every element of a kind. Per-element style_* is the last layer.

MCP tools

Read first

ToolWhat you use it for
list_element_typesIndex by category (layout, form, overlay, …). Call once per group; pass the returned kind to add_element. Do not pass category to add.
inspect_element_typeRequired before any composite primitive. Canonical composition + pitfalls.
list_examplesCopy-paste patterns by kind / intent when the inspect doc is not enough.
load_page_outline / load_component_outlineWhole tree. Heavy — prefer find when you can.
find_element_in_page / find_element_in_componentLabel / kind / text → ids.
inspect_elementOne node's props, styles, renderIf, repeat.

Write

ToolWhat you use it for
add_root_elementFirst node of an empty component tree. Pages already have body. Root kind = Primitive only.
add_elementOne child. parentElementId omitted → under the root.
add_elementsA subtree in one persist. Parent command before child; mint elementId on parents so later commands can point at them. Prefer this over N add_element calls.
update_propsProp slots: children, href, src, instance fields, expressions, function bindings. Set/unset commands — not a full replace.
update_elementNode fields: kind, repeatable, repeatSource, renderIf.
style_*Incremental, breakpoint-aware style (style_layout, style_typography, style_spacing_padding, …). Discover keys with style_schema.
move_elementReparent / reorder. Same targetPosition rules. Cannot move the root or into itself.
remove_elementDelete nodes. mode: "withReparent" unwraps a wrapper and keeps children. This is not delete_component.
duplicate_element_treeDeep copy as the next sibling. New ids. Cannot duplicate the root.
bind_propInside a component: {{props.<key>}} onto a target prop.

add_element

ArgumentRequiredDescription
containeryesPage or component, as in the table above.
kindyesPrimitive or Component, as in Kind.
parentElementIdnoOmit → under the root (body on a page).
targetPositionnoOmit to append. 0 = first.
propsnoArray of { key, value } with string / number / boolean only. Use className, never class.

Objects, expressions that are not a whole {{…}} string, and function bindings go on update_props after create. Set aria-label to 1–3 words of purpose (Hero, Pricing, Close) and put Tailwind in className.

update_element (repeat)

CommandValue
set repeatabletrue
set repeatSource"{{props.events}}" (a whole {{…}})

Event prop

One function binding per slot — sequences live on store actions, not on the element. update_props set on onClick (or onChange, …) with functionId + instanceParams. See Function and Action.

Gotchas

  • aria-label is required on add, and it is a real DOM attribute. 1–3 words of purpose: "Hero", "Pricing", "Close". Not "div", "section wrapper", or a visual description. The tree and find_element_* use it as the label. A gratuitous label on a node that already has visible text overrides that text in the accessibility tree — still set a purpose name; do not use it as a fake editor tag.
  • Omit targetPosition to append. targetPosition: N inserts at index N (the current N and everything after shift down). 0 = first. Passing children.length is the off-by-one: with 6 children (0..5), 5 lands before the last. To make it last, omit the field. Same rule on move_element.
  • inspect_element_type before composites. Blocking for Radio / RadioGroup, SelectItem, MenuItem, Dialog (Trigger / Popup / Header / Title / Description / Panel / Footer / Close), Field + FieldLabel, InputGroup, AccordionTrigger, … Skip only unambiguous leaves (div, span, p, h1h6, a, img, hr, br). Wrong wrappers fail silently (stacked radios, popups outside their trigger).
  • Three write tools, three jobs. update_props = prop slots (including children and function bindings). update_element = kind / repeat / renderIf. style_* = style. Commands are set or unset on a key — last write wins; never send the whole object.
  • Place it once. parentElementId + targetPosition on insert. Do not add then move_element. For a whole subtree, add_elements (parent before child).
  • repeatSource is one whole binding. {{props.events}} or {{stores.x.items}}, not a fragment.

On this page