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

<for>

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

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>

<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.

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 personPromise = new Promise((resolve, reject) => {
  setTimeout(() => resolve({ name: 'Frank' }), 1000);
});

<await(personPromise) client-reorder=true>
  <@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 personPromise = new Promise((resolve, reject) => {
    setTimeout(() => resolve({ name: "Frank" }), 1000);
  });
}

await(personPromise) client-reorder
  @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]-->
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.

Chat in Marko's Discord Server