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 theRegExp
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" -- HelloWith a value of
true
foractive
, the output would be the following:HTML Output<div class="tab-active">Hello</div>With a value of
false
foractive
, 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 contentGiven a value of
"http://localhost/"
for theinput.linkUrl
variable: , the output would be the following:HTML Output<a href="http://localhost/"> Some body content </a>Given a value of
undefined
for theinput.linkUrl
variable: , the output would be the following:HTML OutputSome 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 textWith
isFancy
astrue
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)
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
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|@separator -- ${item} (oops, ReferenceError)-- ${item.name}
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.
Contributors
Helpful? You can thank these awesome people! You can also edit this doc if you see any issues or want to improve it.