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. Use with client-reorder to ensure that the current <await> will always render alongside or 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.

Regardless of these attributes, the promise is executed as eagerly as possible. The attributes control how to coordinate rendering with the rest of the page:

  • client-reorder prevents <await> blocks from delaying the HTTP stream, at the expense of making their rendering rely on client-side JS. Useful for making non-critical page sections not block HTML streaming of important content.

  • Using show-after with client-reorder ensures that the current <await> block will always render simultaneously with or after the named <await>. Useful for cutting down on layout shift. <@placeholder>s can help fine-tune the user experience while loading.

  • timeout is useful for limiting non-critical content from slowing down the rest of the page too much.

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 on GitHub

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