Create design trees with nested JSX markup. One jsx call builds a complete subtree atomically — nesting is the hierarchy. Keep a single logical unit inside one call; the returned root's children are already built, not stubs to be filled in later.
Examples:
jsx({markup: "<frame name='Card' layout='column' padding={16} fill='#FFFFFF' w='fill' />"})
jsx({markup: "<frame name='Row' layout='row' gap={8} padding={12} w='fill'><icon name='lucide:settings' size={20} /><text name='Label' w='fill'>Account</text><icon name='lucide:chevron-right' size={16} /></frame>"})
Elements: frame, text, rect, ellipse, line, icon, image, instance, component, group, section, vector
Attributes (frame): layout, justify, items, wrap, w, h, minW, maxW, p, gap, bg, fill, rounded, stroke, shadow, blur, bgblur, opacity, layoutPositioning
Attributes (grid container): cols, rows, gap, rowGap, colGap, colSizes, rowSizes, autoFlow, autoRows — see "Grid layout" below
Attributes (grid child): colSpan, rowSpan, alignX, alignY, rowStart, colStart
Attributes (text): size, weight, lineHeight, font, fill, w (w="fill" for wrap), maxLines, textTruncation, paragraphSpacing
Multi-paragraph text: put a BLANK LINE between paragraphs inside <text> — it becomes a real paragraph break. A single newline is collapsed to a space (prose wrap); for a hard single-line break write {'\n'}. Pair multi-paragraph body copy with paragraphSpacing={N} for gap between paragraphs.
Truncation: maxLines={N} caps a wrapping text at N lines with an ellipsis (needs a bounded width, e.g. w="fill" or w={240}).
Advanced text — inline emphasis spans (**bold** *italic* ~~strike~~ in content), coloured/sized spans, vertical trim, hanging indent/punctuation, list spacing → read skill:typography (don't inline these here).
Effects: shadow="0,8,32,0,#0006" or shadow={shadow(0,8,32,0,'#0006')}; blur={10} for layer blur; bgblur={20} for frosted-glass/glassmorphism background blur. Multiple effects merge automatically.
Decoration in auto-layout: floating orbs/blobs/decorative shapes inside a row/column parent need layoutPositioning="absolute" so they don't get stacked into the main-axis flow.
Full-frame backgrounds: set the parent frame's bg directly. Don't add a separate <rect> backdrop. Supported gradient strings (CSS-like subset, not full CSS):
- linear-gradient(<angle>deg, <#hex> <pos>%, ...) e.g. "linear-gradient(135deg, #A 0%, #B 100%)"
- linear-gradient(to <direction>, <stops>) directions: top/right/bottom/left and corners (e.g. "to bottom right")
- radial-gradient(<stops>) centered, ellipse-fill — no position/shape modifiers
- radial-gradient(circle, <stops>) centered, circle shape only
- conic-gradient(from <angle>deg, <stops>)
Unsupported (will be rejected): "circle at X% Y%", "ellipse at ...", "X% Y%" position syntax, named colors (red/blue/...), hsl(), and numeric stops without a percent sign.
Text: <text size={24}>content here</text>
Rich text (mixed inline styles in one text node — runtime parses markup per character):
**bold** e.g. "Click **here** to continue"
*italic* e.g. "Read the *fine print*"
***bold italic*** e.g. "This is ***critical***"
~~strikethrough~~ e.g. "Was ~~$19.99~~"
{color:#HEX|text} e.g. "{color:#EF4444|Error}: something failed"
{size:N|text} e.g. "{size:32|Big} then normal"
Nesting stacks: "{color:#EF4444|**$9.99**}" → red + bold on the same range. For uniform whole-text styling use the weight / fill / size props instead — markup is only for MIXED styles within one text node.
Instance: <instance ref="Button" variant="Size=Large"/>
Self-closing: <line w="fill" stroke="#E5E7EB"/> (use line for dividers/separators; rect/ellipse for SMALL pure decoration with no children — page-level backgrounds belong on the parent frame's bg)
Arc/Ring: <ellipse w={120} h={120} arc="0 270" fill="#4F46E5"/> (arc="start end innerRadius?" — innerRadius 0-1 makes a donut/ring)
Grid layout: `layout="grid"` + `cols={N}`. Omit `rows` for auto-rows (rows grow to fit children); set `rows={M}` for a fixed N×M. `gap`, or `rowGap`/`colGap` separately.
- Per-track sizing: `colSizes="240px 1fr 1fr"` (fixed sidebar + two flexible columns) or `rowSizes="80px 1fr"` (fixed header row + flexible body). Tokens: `Npx`/`N`=fixed px, `Nfr`=flexible share (`1fr 2fr` ⇒ 1:2), `hug`=fit-content. Setting `colSizes`/`rowSizes` infers `cols`/`rows` from the token count — don't also pass a conflicting count.
- Auto-flow: `autoFlow` packs children row-major into the next free cell (best for galleries/masonry, esp. with mixed spans). `autoRows` lets the grid manage its own row count.
- Per-child: `colSpan`/`rowSpan` (cell spanning), `alignX`/`alignY` (start|center|end|auto within the cell), `rowStart`/`colStart` (0-based explicit cell anchor — requires manual placement, so do NOT combine with `autoFlow`).
Sizing defaults (lean on them): rows are HUG (cells fit content) and the container HUGS its height automatically, while children default to fill-width / hug-height. So a content gallery needs only `cols` + a `w` (fixed px or `"fill"`) — no `rowSizes`, no height. For equal-height rows (KPI tiles, dashboards) set `rowSizes="1fr 1fr"` (children then FILL the rows); for fixed-height bands use px row sizes. A `rowSpan>1` child fills its span.
Gotchas: (1) the container can hug HEIGHT but not WIDTH (columns are FLEX) — always give it a `w` (`"fill"` or px). (2) `justify`/`items` are no-ops on a grid — use per-child `alignX`/`alignY`. (3) for a 1-column stack use `layout="column"`, not grid. Prefer grid for a 2-D matrix (galleries, KPI tiles, pricing tiers, dashboards); use row/column when children differ in width or you need space-between.
Variable binding: fill/bg/stroke accept qualified bare-name token strings (e.g. bg="$Theme/Bg/Surface"). Object literals (fill={{variable_id:...}}) drop the binding silently — always use the string form.
Swap an existing subtree: jsx({replaceId: "<id>", markup: "..."}) replaces the old node at the same parent and sibling index atomically, preserving position in one call. Markup must have a single root. Use jsx for tree creation; edit for property updates on known nodes.