Interpolation and escaping

${} and $!{}

${expr} interpolates a value and escapes it for safe output:

<p>Hello, ${user.name}</p>

If user.name is <script>, the output contains the escaped entities, not a literal tag.

$!{expr} interpolates without escaping — use it only when the expression already produces markup you trust:

<div>$!{renderedMarkdown}</div>

$!{...} is not accepted inside an attribute value at all; Marko’s own parser rejects it there before any host runs.

A ${expr} on its own line (concise mode’s tag position, not inside an element’s body) is not a text placeholder — it’s a dynamic tag, the concise form of <${expr}/>. Write -- ${expr} to put an expression on its own line as text.

${MyComponent}
-- ${user.name}

The first line renders MyComponent as a component; the second interpolates user.name as text. ${expr} written inside an element’s body (like <p>Hello, ${user.name}</p> above) stays an ordinary placeholder — this rule only applies to a standalone line.

Whitespace

MX follows Marko’s whitespace rule, not JSX’s:

  • A whitespace-only run of text that contains a newline is dropped entirely. This means ordinary indentation between tags produces no extra text nodes or spaces in the output.
  • A whitespace-only run without a newline collapses to a single space.
  • ${" "} is the escape hatch when you need a literal space that the newline rule would otherwise drop.
  • Comments don’t count as content when the whitespace rule decides what to trim.
<p>
  Hello
</p>

renders <p>Hello</p> — the newlines and indentation around Hello are dropped, not preserved as whitespace.

HTML comments

<html-comment> renders as a literal HTML comment, with any ${} placeholders inside it escaped by a comment-safe rule that differs from ordinary text: only > is escaped, while <, &, and quotes pass through raw. This matches how Marko itself escapes comment content, and it means a placeholder like a commit SHA can appear inside a comment without being mangled:

<html-comment>build ${input.sha}</html-comment>