Marko

Syntax

Marko is HTML re-imagined as a language for building dynamic and reactive user interfaces. Just about any valid HTML is valid Marko, but Marko extends the HTML language to allow building modern applications in a declarative way.

ProTip: Marko also supports a beautiful concise syntax. If you'd prefer to see the documentation using this syntax, just click the switch syntax button in the corner of any Marko code sample.

Note: Text at the root of a template (outside any tags) must be prefixed with the concise syntax's -- to denote it is text. The parser starts in concise mode and would otherwise try to parse what you meant to be text as a concise tag declaration.

-- Root level text
-- Root level text

Tags

As you might expect, Marko supports all native HTML/SVG/whatever tags and attributes. In addition to these, it also comes with a set of useful core tags. Beyond this, you can also build your own custom tags and install third-party tags from npm.

All of these types of tags use the same syntax:

<my-tag-name/>
my-tag-name

You don't need to import tags. Marko discovers them based on the folder structure—similar to how you don't specify a full path when referencing a module in node_modules/. Marko looks in components/ by default and this directory can be configured in marko.json.

Dynamic text

You can use placeholders (${}) to insert a value into the template: Placeholders accept any JavaScript expression and the result of the expression will be inserted into the HTML output:

<div>
    Hello ${"world".toUpperCase()}
</div>
div -- Hello ${"world".toUpperCase()}

These values are automatically escaped so you don't accidentally insert malicious code. If you do need to pass unescaped HTML, you can use $!{}:

<div>
    Hello $!{"<b>World</b>"}
</div>
div -- Hello $!{"<b>World</b>"}

ProTip: If necessary, you can escape $ using a backslash to have it be treated as text instead of a placeholder token:

<div>
    Placeholder example: <code>\${someValue}</code>
</div>
div
  -- Placeholder example:
  code -- ${someValue}

Attributes

In marko attributes are parsed as JavaScript expressions (instead of just strings).

<div class=myClassName/>
<input type="checkbox" checked=isChecked/>

<custom-tag string="Hello"/>
<custom-tag number=1/>
<custom-tag template-string=`Hello ${name}`/>
<custom-tag boolean=true/>
<custom-tag array=[1, 2, 3]/>
<custom-tag object={ hello: "world" }/>
<custom-tag variable=name/>
<custom-tag function-call=user.getName()/>
div class=myClassName
input type="checkbox" checked=isChecked

custom-tag string="Hello"
custom-tag number=1
custom-tag template-string=`Hello ${name}`
custom-tag boolean
custom-tag array=[1, 2, 3]
custom-tag object={ hello: "world" }
custom-tag variable=name
custom-tag function-call=user.getName()

Attributes that are passed to a custom tag are received as it's input.

Note: Although in most cases you won't see a difference, strings are parsed as JavaScript strings, not HTML strings. Where this comes up most often is using the pattern attribute with the <input> tag: you need to "double escape" your regex escape sequences much like you were passing a string to the RegExp constructor (or you can use a literal /regex/).

Marko Source
<input pattern="\\w+" type="text"/>
<input pattern=/\w+/ type="text"/>
input pattern="\\w+" type="text"
input pattern=/\w+/ type="text"
HTML Output
<input pattern="\w+" type="text" />

Complex expressions

Any JavaScript expression is a valid attribute value, provided it meets the following criteria:

It does not contain any spaces

It does not contain any right angle brackets (>)

<custom-tag sum=1+2 difference=3-4/>
custom-tag sum=1 + 2 difference=3 - 4
custom-tag sum=1+2 difference=3-4
custom-tag sum=1 + 2 difference=3 - 4

Spaces and > are contained within matching (), [], {}, strings and regexps

<custom-tag sum=(1 + 2) difference=(3 - 4) greater=(1 > 2)/>
custom-tag sum=1 + 2 difference=3 - 4 greater=(1 > 2)
custom-tag sum=(1 + 2) difference=(3 - 4) greater=(1 > 2)
custom-tag sum=1 + 2 difference=3 - 4 greater=(1 > 2)

Boolean attributes

HTML defines the following rules for boolean attributes:

The presence of a boolean attribute on an element represents the true value, and the absence of the attribute represents the false value.

In Marko when an attribute value evaluates to false, null, or undefined, the attribute is not included in the output. If an attribute value is true, only the attribute name is included in the output.

Marko Source
<input type="checkbox" checked=true>
<input type="checkbox" checked=false>
input type="checkbox" checked
input type="checkbox" checked=false

Renders the following HTML:

HTML Output
<input type="checkbox" checked /> <input type="checkbox" />

Similarly, when only an attribute name is defined, it is equivalent to specifying the attribute with a value of true:

<!-- These are equivalent -->
<custom-menu expanded/>
<custom-menu expanded=true/>
<!-- These are equivalent -->
custom-menu expanded
custom-menu expanded

ProTip: You can take advantage of the way Marko handles boolean attributes to conditionally render attributes:

Marko Source
<div class=(active && "tab-active")>Hello</div>
div class=active && "tab-active" -- Hello

With a value of true for active, the output would be the following:

HTML Output
<div class="tab-active">Hello</div>

With a value of false for active, the output would be the following:

HTML Output
<div>Hello</div>

Dynamic attributes

The spread syntax (...) can be used to merge in an object as attributes to a tag:

Marko Source
<a ...attrs target="_blank">eBay</a>
a ...attrs target="_blank" -- eBay

With attrs as the following value:

{
    class: "active",
    href: "https://ebay.com/"
}

would output the following HTML:

HTML Output
<a class="active" href="https://ebay.com/" target="_blank">eBay</a>

ProTip: With spread attributes order matters. You can take advantage of this to implement both default attributes, and enforced attributes.

<custom-tag ...defaults ...userSupplied class="overridden"/>
custom-tag.overridden ...defaults ...userSupplied

ProTip: You can provide undefined to a spread attribute which will output nothing.

Style attribute

You can pass a string as the value of style just as you would in HTML, in addition Marko supports passing an object or array as the value of the style attribute:

Marko Source
<!-- string: -->
<div style="display:block;margin-right:16px"/>

<!-- object: -->
<div style={ display: "block", color: false, marginRight: 16 }/>

<!-- array: -->
<div style=["display:block", null, { marginRight: 16 }]/>
<!-- string: -->
div style="display:block;margin-right:16px"
<!-- object: -->
div style={ display: "block", color: false, marginRight: 16 }
<!-- array: -->
div style=["display:block", null, { marginRight: 16 }]

In all cases, the output will be the same:

HTML Output
<div style="display:block;margin-right:16px;"></div>

Class attribute

The class attribute also supports receiving an object or array (in addition to a string) as shown below:

Marko Source
<!-- string: -->
<div class="a c"/>

<!-- object: -->
<div class={ a:true, b:false, c:true }/>

<!-- array: -->
<div class=["a", null, { c:true }]/>
<!-- string: -->
div.a.c
<!-- object: -->
div class={
  a: true,
  b: false,
  c: true,
}
<!-- array: -->
div class=[
  "a",
  null,
  {
    c: true,
  },
]

In all cases, the output will be the same:

HTML Output
<div class="a c"></div>

Shorthand attributes

Marko provides a shorthand for declaring classes and ids on an element, including interpolation. Given size is the string small:

Marko Source
<div.my-class/>
<span#my-id/>
<button#submitprimary.large/>
<button.button--${size}></button>
div.my-class
span#my-id
button.large#submitprimary
button class=`button--${size}`

Renders the following HTML:

HTML Output:

<div class="my-class"></div>
<span id="my-id"></span>
<button id="submit" class="primary large"></button>
<button class="button--small"></button>

Parameters

When a tag renders its body content, it may provide data which can be received by defining parameters after the tagname. Parameters are available to the tag's body content.

This is a powerful feature that allows components to provide functionality and data while giving you full control over what gets rendered.

In the following example, <mouse> provides a parameter which we have named position:

<mouse|position|>
   The mouse is at ${position.x}, ${position.y}!
</mouse>
mouse|position| -- The mouse is at ${position.x}, ${position.y}!

<mouse> would render its body and provide the position similar to this: <${input.renderBody} x=0 y=0/>.

ProTip: Tag |parameters| are treated as regular JavaScript function parameters. This means you can destructure, set default values, etc.

<mouse|{ x, y }|>
  The mouse is at ${x}, ${y}!
</mouse>
mouse|{ x, y }| -- The mouse is at ${x}, ${y}!

Note: Parameters are not available to attributes, only to the tag body.

<mouse|position| something=position>
  ReferenceError when setting the "something" attribute
</mouse>
mouse|position| something=position
  -- ReferenceError when setting the "something" attribute

Parameters are used by some of Marko's core tags like the <for> and <await> tags.

Arguments

Some tags and attributes accept javascript style arguments. Arguments are denoted by parenthesis following the tag or attribute name. Arguments provide a way to pass unnamed data to a tag.

<if(true)>
    <strong>Marko is awesome</strong>
</if>

<h1 body-only-if(skipHeading)>
    Conditional display heading, but always show content!
</h1>
if(true)
  strong -- Marko is awesome

h1 body-only-if(skipHeading)
  -- Conditional display heading, but always show content!

Arguments are used by some of Marko's core tags like the <if> tag and body-only-if attribute displayed above.

Previously you could also use them in your own custom tags however it is now recommended to use dynamic attributes.

Dynamic tagname

The <${dynamic}> syntax is used to render a tag or component that isn't determined until runtime. It can also be used within a custom tag to render body content that was passed to that tag.

Marko Source
<${href ? 'a' : 'button'} href=href>
    Click me!
</>
${href ? "a" : "button"} href=href -- Click me!

With href as https://ebay.com would output the following HTML:

HTML Output
<a href="https://ebay.com">Click me!</a>

And with href as undefined would output the following HTML:

HTML Output
<button>Click me!</button>

As a shorthand if there is a variable in scope and no other matching tag is discovered the wrapping ${} is unnecessary.

For example the following are equivalent:

$ const MyTag = href ? 'a' : 'button';
<${MyTag}/>
<MyTag/>
$ const MyTag = href ? "a" : "button";
${MyTag}
MyTag

ProTip: If you find that you have a wrapper element that is conditional, but whose body should always be rendered then you can use a null dynamic tag. For example, to only render a wrapping <a> tag if there is a valid URL then you could do the following:

Marko Source
<${input.linkUrl ? "a" : null} href=input.linkUrl >
   Some body content
</>
${input.linkUrl ? "a" : null} href=input.linkUrl -- Some body content

Given a value of "http://localhost/" for the input.linkUrl variable: , the output would be the following:

HTML Output
<a href="http://localhost/"> Some body content </a>

Given a value of undefined for the input.linkUrl variable: , the output would be the following:

HTML Output
Some body content

Dynamic components

Instead of just strings, the dynamic tagname can also be a component:

import componentA from "<component-a>";
import componentB from "<component-b>";

<${useA ? componentA : componentB}/>
import componentA from "<component-a>";
import componentB from "<component-b>";

${useA ? componentA : componentB}

ProTip: You can also switch between a normal HTML tag and a component:

import FancyButton from "<fancy-button>";

<${isFancy ? FancyButton : 'button'}>
    Button text
</>
import FancyButton from "<fancy-button>";

${isFancy ? FancyButton : "button"} -- Button text

Note: You cannot reference a Marko custom tag or macro using a name string:

Marko Source
<${isFancy ? 'fancy-button' : 'button'}>
    Button text
</>
${isFancy ? "fancy-button" : "button"} -- Button text

With isFancy as true would output the following HTML:

HTML Output
<fancy-button>Button text</fancy-button>

Dynamic body content

When a custom tag receives body content, it is passed as a renderBody property. To render this content you can pass the renderBody as the dynamic tagname.

<div class="container">
    <${input.renderBody}/>
</div>
div.container
  ${input.renderBody}

Attribute Tag

As the name implies, <@attribute-tags> are special attributes that take the form of tags. They allow you to pass named body sections to a custom tag.

The core <await> tag allows you to pass multiple body sections that it will conditionally render based on the state of the promise.

<await(somePromise)>
    <@then|result|>
        The promise resolved: ${result}
    </@then>
    <@catch|error|>
        The promise rejected: ${error.message}
    </@catch>
</await>
await(somePromise)
  @then|result| -- The promise resolved: ${result}
  @catch|error| -- The promise rejected: ${error.message}

These body sections are also commonly used to create layouts:

<page-layout>
    <@heading>
        <h1>Hello</h1>
    </@heading>
    <@body>
        <p>Lorem ipsum....</p>
    </@body>
</page-layout>
page-layout
  @heading
    h1 -- Hello
  @body
    p -- Lorem ipsum....

These tags are passed to the custom tag as objects with a renderBody, it can then render its body content.

Note: Attribute tags can have their own parameters, but like attributes, they cannot access the parameters of their parent tag:

<list|item|>
  ${item.name}
  <@separator>${item} (oops, ReferenceError)</@separator>
</list>
list|item|
  -- ${item.name}
  @separator -- ${item} (oops, ReferenceError)

Inline JavaScript

To execute JavaScript in your template you can insert a Javascript statement using the $ <code> syntax.

A line that starts with a $ followed by a space will execute the code that follows.

$ const name = "World";

<div>
    Hello, ${name}
    $ console.log("The value rendered was", name);
</div>
$ const name = "World";

div
  -- Hello, ${name}
  $ console.log("The value rendered was", name);

A statement may continue onto subsequent lines if new lines are bounded by {}, [], (), ``, or /**/:

$ const person = {
    name: "Frank",
    age: 32
};
$ const person = {
  name: "Frank",
  age: 32,
};

Multiple statements or an unbounded statement may be used by wrapping the statement(s) in a block:

$ {
    const bgColor = getRandomColor();
    const textColor = isLight(bgColor)
        ? "black"
        : "white";
}
$ const bgColor = getRandomColor();
$ const textColor = isLight(bgColor) ? "black" : "white";

ProTip: Any JavaScript statement can be used here, even debugger:

<div>
    ${textColor}
    $ debugger; // Quickly debug `textColor`
</div>
div
  -- ${textColor}
  $ debugger; // Quickly debug `textColor`

ProTip: If necessary, you can escape $ using a backslash to have it be treated as text instead of a placeholder token:

<p>You can run JS in a Marko template like this:</p>
<code>
    \$ var num = 123;
</code>
p -- You can run JS in a Marko template like this:
code -- \\$ var num = 123;

ProTip: If you find yourself writing a lot of inline JS, consider moving it out to an external file and then import it.

Static JavaScript

Inline JavaScript will run each time your template is rendered, but the JavaScript code that follows static will only run once when the template is loaded. It must be declared at the top level and does not have access to values passed in at render time.

static var count = 0;
static var formatter = new Formatter();

static function sum(a, b) {
    return a + b;
};

<div>${formatter.format(sum(2, 3))}</div>
static var count = 0;
static var formatter = new Formatter();
static function sum(a, b) {
  return a + b;
}

div -- ${formatter.format(sum(2, 3))}

Like inline Javascript, multiple statements or an unbounded statement may be used by wrapping the statement(s) in a block:

static {
    var base = 2;
    function sum(a, b) {
        return base + a + b;
    };
}
static var base = 2;
static function sum(a, b) {
  return base + a + b;
}

Importing external files

The import statement is used to access data and functions from external files. It follows the same syntax as the JavaScript import statement.

import sum from './utils/sum';
<div>The sum of 2 + 3 is ${sum(2, 3)}</div>
import sum from "./utils/sum";
div -- The sum of 2 + 3 is ${sum(2, 3)}

As a shorthand you can also import components by providing it's html tag name wrapped in angle brackets, eg:

import MyComponent from "<my-component>"
import MyComponent from "<my-component>";

This is especially useful with the dynamic tag name syntax and uses the same component discovery as if the tag was used in the template.

Comments

Standard HTML comments can be used and will be stripped out of the rendered output. At the top level of the template JavaScript comments (// comment and /** comment */) can also be used.

<!-- This is a comment that will not be rendered -->

<h1>Hello</h1>
<!-- This is a comment that will not be rendered -->

h1 -- Hello

If you would like for your HTML comment to show up in the final output then you can use the html-comment core tag.

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