Skip to content

Elements

Studio writes this format for you. Every element you place on the canvas and edit in the Properties panel is stored as one of these objects — this page documents what lands in the file.

An element definition is a JSON object describing one DOM element. Its keys are the element's real DOM property names — set directly on the created element, with no translation layer in between. If a property exists on the DOM element, you can set it here.

{
  "tagName": "div",
  "id": "my-element",
  "className": "container active",
  "hidden": false,
  "tabIndex": 0,
  "textContent": "Hello World"
}

DOM properties

Property names follow the DOM, not HTML source: className (not class ), textContent (not inner text), tabIndex (not tabindex ). Any string-valued property may contain a template for a reactive value (see Reactivity ), or be an object with $ref bound to state (see References ):

{
  "tagName": "div",
  "textContent": "undefined items remaining",
  "hidden": 
}

Two properties are protected : id and tagName identify the element and may not be set via $ref bindings.

Custom attributes

Anything that is not a standard DOM property — data-* , aria-* , slot — goes in the attributes object, written exactly as it appears in HTML:

{
  "tagName": "div",
  "attributes": {
    "data-component": "my-widget",
    "aria-label": "Interactive counter",
    "slot": "header"
  }
}

Attribute values may also be reactive templates ( "aria-label": "undefined unread messages" ).

Children

children is an array of element definitions, rendered in order:

{
  "tagName": "div",
  "children": [
    { "tagName": "h1", "textContent": "Title" },
    { "tagName": "p", "textContent": "Content" }
  ]
}

A children array may freely mix element objects, bare text nodes, and repeaters ( $prototype: "Array" members) or switches .

Text nodes

Bare strings and numbers are valid children items. They produce DOM Text nodes directly, with no wrapper element — this is how text with inline markup is written:

{
  "tagName": "p",
  "children": ["Hello ", { "tagName": "strong", "textContent": "world" }, "!"]
}

That is the HTML <p>Hello <strong>world</strong>!</p> . Template strings in text nodes are reactive: { "children": ["Welcome, undefined!"] } .

Slots

Custom elements use the standard HTML slot mechanism for content composition. A component's template places <slot> elements; content the instance provides is distributed to them by name :

{
  "tagName": "card-component",
  "children": [
    {
      "tagName": "header",
      "children": [{ "tagName": "slot", "attributes": { "name": "header" } }]
    },
    { "tagName": "main", "children": [{ "tagName": "slot" }] }
  ]
}

A slot's own children act as fallback content, kept when the instance provides nothing for it.

Annotations

Any element may carry $title and $description — developer-facing labels that never reach the DOM:

{
  "tagName": "section",
  "$title": "Hero Section",
  "$description": "Primary landing area with headline and call-to-action",
  "children": []
}

Studio's Layers panel shows $title as the element's display name.

How it works

The runtime creates the element with document.createElement(tagName) , assigns each listed property directly on the element object, and writes attributes entries with setAttribute . Properties whose values are templates or $ref bindings are wrapped in reactive effects, so the DOM updates whenever the underlying state changes. Slot distribution is manual light-DOM distribution: host children are captured before the template renders, then moved to matching <slot> elements by name .

Rules

  • tagName is required on every element definition.

  • id and tagName are protected — never settable via $ref .

  • Standard DOM properties go at the top level; everything else ( data-* , aria-* , slot ) goes in attributes .

  • Bare strings and numbers in children become text nodes; when all children are bare strings with no element siblings, prefer textContent instead.

  • $title and $description are plain strings — not reactive, not $ref -resolvable, never applied to the DOM.

  • Styling does not use DOM properties — the style object has its own grammar, covered in Styling .