Marko

Core tags and attributes

Much like HTML has its own native tags, Marko includes core tags and global attributes for declaratively building modern applications.

<if>, <else-if>, <else>

Like the equivalent JavaScript statements, these tags render conditional content:

<if(arriving)>
  Hey there
</if>
<else-if(leaving)>
  Bye now
</else-if>
<else>
  What’s up?
</else>
if(arriving) -- Hey there
else-if(leaving) -- Bye now
else -- What’s up?

They support any JavaScript expression in their tag arguments:

<if(Math.random() > 0.5)>
  <p>50% chance to see this</p>
</if>
if(Math.random() > 0.5)
  p -- 50% chance to see this

Note: The alternate conditional attribute syntax is deprecated:

<p if(arriving)>Hey there</p>
<p else-if(leaving)>Bye now</p>
<p else>What’s up?</p>
p if(arriving) -- Hey there
p else-if(leaving) -- Bye now
p else -- What’s up?

<for>

The <for> tag iterates over arrays/array-likes, object properties, and ranges of numbers.

Note: You may see for() as a tag or attribute. This kinda-like-JS-but-not-really syntax is deprecated:

<li for(color in colors)>${color}</li>
li for(color in colors) -- ${color}

Iterating over a list

Like the JavaScript for...of loop statement, giving <for>’s of attribute a value will loop over that value as an array or iterable.

The current item, index, and the iterating list are provided as tag parameters:

$ const colors = ["red""green""blue"];
<ol>
  <for|color, index, colorList| of=colors>
    <li value=index>${color}</li>
  </for>
</ol>
$ const colors = ["red""green""blue"];
ol
  for|color, index, colorList| of=colors
    li value=index -- ${color}

The output HTML would be:

<ol>
  <li value="0">red</li>
  <li value="1">green</li>
  <li value="2">blue</li>
</ol>

Pro Tip: <for>’s of attribute can loop over any iterable, just like JavaScript’s for...of. This includes strings, NodeLists, Sets… any object with zero-indexed numeric properties and a .length, basically.

Iterating over an object’s properties

Like JavaScript’s for...in loop statement, giving <for> an object as its in attribute will loop over that object’s properties.

The current property name and property value are provided as tag parameters:

$ const settings = {
  "Dark Mode"false,
  "Fullscreen"true
};
 
<dl>
  <for|name, enabled| in=settings>
    <dt>${name}:</dt>
    <dd>${enabled ? "on" : "off"}</dd>
  </for>
</dl>
$ const settings = {
  "Dark Mode"false,
  Fullscreen: true
};
dl
  for|name, enabled| in=settings
    dt -- ${name}:
    dd -- ${enabled ? "on" : "off"}

The output HTML would be:

<dl>
  <dt>Dark Mode:</dt>
  <dd>off</dd>
  <dt>Fullscreen:</dt>
  <dd>on</dd>
</dl>

Iterating between a range of numbers

The final <for> variant loops between two numbers, by providing from and to attributes. The current number in the range will be provided as a tag parameter:

<ol type="I">
  <for|i| from=0 to=10>
    <li value=i>${i}</li>
  </for>
</ol>
ol type="I"
  for|i| from=0 to=10
    li value=i -- ${i}

You can also pass an optional step attribute, which defaults to 1 otherwise. step lets you increment by a specific amount:

<ol type="I">
  <for|i| from=0 to=10 step=2>
    <li value=i>${i}</li>
  </for>
</ol>
ol type="I"
  for|i| from=0 to=10 step=2
    li value=i -- ${i}

…becomes:

<ol type="I">
  <li value="0">0</li>
  <li value="2">2</li>
  <li value="4">4</li>
  <li value="6">6</li>
  <li value="8">8</li>
  <li value="10">10</li>
</ol>
ol type="I"
  li value="0" -- 0
  li value="2" -- 2
  li value="4" -- 4
  li value="6" -- 6
  li value="8" -- 8
  li value="10" -- 10

ProTip: This syntax is for generating numbers from nothing. Don’t use it to iterate over an object, like so:

<!-- Inefficient code, do not copy -->
<ul>
  <for|i| from=0 to=(myArray.length - 1)>
    <li>${myArray[i]}</li>
  </for>
</ul>
// Inefficient code, do not copy
ul
  for|i| from=0 to=(myArray.length - 1)
    li -- ${myArray[i]}

Use <for of> instead.

<while>

Warning: Using <while> is not recommended. Instead, replicate it with an iterable and <for>.

In the future, Marko may restrict value mutation during rendering, for runtime optimizations.

You can repeat a chunk of markup until a condition is met with the while tag:

$ let n = 0;
 
<while(n < 4)>
  <p>${n++}</p>
</while>
$ let n = 0;
while(n < 4)
  p -- ${n++}

…becomes:

<p>0</p>
<p>1</p>
<p>2</p>
<p>3</p>

Note: while as an attribute is deprecated:

$ let n = 0;
 
<p while(n < 4)>${n++}</p>
$ let n = 0;
p while(n < 4) -- ${n++}

<macro>

Macros create reusable markup fragments for later use in the same template they were defined in.

The <macro> tag defines a macro as a tag via the name attribute. For example, the following macro is registered as the <greeting> tag:

<macro name="greeting">
  <p>Welcome!</p>
</macro>
 
<greeting/>
<greeting/>
macro name="greeting"
  p -- Welcome!
greeting
greeting

…the output HTML would be:

<p>Welcome!</p>
<p>Welcome!</p>

Macros become more useful with tag parameters, allowing complex templates. In this next example, <greeting> can now receive firstName and count parameters from its parent:

<macro|{ firstName, count }| name="greeting">
  <p>Hello ${firstName}!
    <output>You have ${count} new messages.</output>
  </p>
</macro>
 
<greeting firstName="Frank" count=20/>
macro|{ firstName, count }| name="greeting"
  p
    --- 
    Hello ${firstName}!
    <output>You have ${count} new messages.</output>
    ---
greeting firstName="Frank" count=20

…the output HTML would be:

<p>
  Hello Frank!
  <output>You have 20 new messages.</output>
</p>

Macros receive input like components do, including a renderBody for provided body content:

<macro|{ renderBody }| name="special-heading">
  <h1>
    <${renderBody}/>!
  </h1>
</macro>
 
<special-heading>
  Hello
</special-heading>
macro|{ renderBody }| name="special-heading"
  h1
    -- <${renderBody}/>!
special-heading -- Hello

…the output HTML would be:

<h1>Hello!</h1>

ProTip: You can use a macro inside itself for recursive layouts, like displaying directory contents.

<await>

The <await> tag renders markup asynchronously using a Promise.

  • Its <@then> attribute tag displays when the Promise resolves, optionally receiving the resolved value as a tag parameter.
  • Its <@catch> attribute tag displays when the Promise rejects, optionally receiving the rejected value as a tag parameter.
  • Its optional <@placeholder> attribute tag displays while the Promise is pending.
$ const personRequest = new Promise((resolve, reject) => {
  setTimeout(() => resolve({ name: 'Frank' })1000);
});
 
<await(personPromise)>
  <@placeholder>
    <!-- Displays while promise is pending -->
    <label>Loading…
      <progress></progress>
    </label>
  </@placeholder>
 
  <@then|person|>
    <!-- Displays if promise resolves -->
    <p>Hello ${person.name}!</p>
  </@then>
 
  <@catch|err|>
    <!-- Displays if promise rejects -->
    ${err.name} error: ${err.message}
  </@catch>
</await>
$ const personRequest = new Promise((resolve, reject) => {
  setTimeout(() => resolve({ name: "Frank" })1000);
});
await(personPromise)
  @placeholder
    // Displays while promise is pending
    label
      -- Loading…
      progress
  @then|person|
    // Displays if promise resolves
    p -- Hello ${person.name}!
  @catch|err|
    <!-- Displays if promise rejects --> ${err.name} error: ${err.message}

Optional attributes for <await>:

AttributeTypeDescription
timeoutintegerAn optional timeout. If reached, rejects the promise with a TimeoutError.
namestringImproves debugging and ensures ordering with the show-after attribute.
show-afterstringAnother <await> tag’s name. With client-reorder, ensures that the current <await> block will always show after the named <await>.
client-reorderbooleanIf true, anything after this <await> will be server-rendered before the Promise completes, then the fulfilled Promise’s result will be updated with client-side JavaScript.

Pro Tip: When using timeout, you can distinguish between TimeoutErrors and promise rejections by checking the error’s name:

<await(slowPromise) timeout=5000>
  <@then>Done</@then>
  <@catch|err|>
    <if(err.name === "TimeoutError")>
      Took too long to fetch the data!
    </if>
    <else>
      Promise failed with ${err.message}.
    </else>
  </@catch>
</await>
await(slowPromise) timeout=5000
  @then -- Done
  @catch|err|
    if(err.name === "TimeoutError") -- Took too long to fetch the data!
    else -- Promise failed with ${err.message}.

<include-text>

<include-text> inlines text files into a template, escaping HTML syntax characters (<, ", etc.).

<include-text('./foo.txt')/>
include-text("./foo.txt")

If you do not want escaping, use <include-html> instead.

<include-html>

Like <include-text>, <include-html> inlines the contents of a file. However, this tag does not escape special HTML characters.

<include-html('./foo.html')/>
include-html("./foo.html")

<html-comment>

Marko removes HTML comment tags from its output. But if you need comments in the output, that’s what <html-comment> is for:

<html-comment>[if IE]><script src="html-shiv.js"></script><![endif]</html-comment>
html-comment -- [if IE]><script src="html-shiv.js"></script><![endif]

…becomes:

<!--[if IE]><script src="html-shiv.js"></script><![endif]-->

Note: You might see the deprecated <marko-compiler-options> tag used to configure comments for the template:

<marko-compiler-options preserve-comments/>
marko-compiler-options preserve-comments

Deprecated

The following tags and attributes are deprecated, but you might see them in older code.

marko-preserve-whitespace

Instead, preserve whitespace with the preserve-whitespace attribute:

style {
  .lang-python {
    white-space: pre-wrap;
  }
}
 
<p>You’ll get an error with that line of Python,
  as it has one too many spaces as indentation:
  <code.lang-python marko-preserve-whitespace>    <mark> </mark>frobulate()</code>
</p>
style {
  .lang-python {
    white-space: pre-wrap;
  }
}
 
p
  --- 
  You’ll get an error with that line of Python,
  as it has one too many spaces as indentation:
  ---
  <code.lang-python marko-preserve-whitespace>    <mark> </mark>frobulate()</code>

marko-body

The marko-body attribute controls how a tag’s body content is parsed, with the following possible values:

  • html (default) — Body content is parsed as HTML.
  • static-text — Body content is parsed as static text, ignoring HTML tags and dynamic text ${placeholders}.
  • parsed-text — Body content is parsed as text, ignoring HTML tags. Does not ignore ${placeholders}.
<p marko-body="static-text">
  This is just one
  <b malformed-attribute=">
    Hello ${THIS IS NOT VALID}!
  </b>
  big text block
</p>
p marko-body="static-text"
  --- 
  This is just one
  <b malformed-attribute=">
    Hello ${THIS IS NOT VALID}!
  </b>
  big text block
  ---

…becomes:

<p>
  This is just one &lt;b malformed-attribute="&gt; Hello ${THIS IS NOT VALID}!
  &lt;/b&gt; big text block
</p>
EDIT

Contributors

Helpful? You can thank these awesome people! You can also edit this doc if you see any issues or want to improve it.