Marko is roughly a superset of the HTML, reimagined as a language for building dynamic and reactive user interfaces. Most .html
files can also compile as .marko
files, but Marko also extends the language to provide tools which 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.
Map of Marko's tag syntax:
<tagName|tag,parameters|/tagVariable ...attributes> renderBody <@attrTag/> </tagName>
Using links like this might only work in HackMD's Markdown Parser Luke
All native HTML tags are also supported in Marko. In addition, Marko comes with a set of core tags which may be used as building blocks for custom tags.
All of these types of tags use the same syntax, which is identical to HTML:
<tag-with-renderBody>
renderBody
</tag-with-renderBody>
<self-closing-tag/>
Attributes are mostly the same as HTML, but their contents are parsed as JavaScript expressions instead of strings. With few exceptions, this is an extension of HTML.
All of the following are valid attributes in Marko:
<my-tag
attrString="hello"
attrNumber=3
attrExpression=5+7
attrVariable=myValue
attrList=["a", "b", "c"]
attrObject={a: "a", b: "b"}
attrFunction=() => {}
/>
ProTip
See the shorthands & concise mode section for a few built-in features that can make attributes easier to read.
Marko's parser is able to figure out when each expression ends and another attribute begins, so no parentheses are needed except in rare ambiguous cases such as the >
operator:
<my-tag isGreater=(4 > 3)></my-tag>
Similarly to HTML, Marko parses attributes without any arguments as true
:
<input type="checkbox" checked>
// identical to
<input type="checkbox" checked=true>
JavaScript-style object spreads are also allowed, where each key and its value are mapped to an attribute.
<input ...{type: "checkbox", checked: true}>
Just like in JavaScript's `template strings`
, tag bodies may use ${placeholders}
to interpolate values:
<div>
Hello ${"world".toUpperCase()}
</div>
Warning
Values inside placeholders are automatically escaped to prevent the injection of malicious code. For cases where the developer wants to insert HTML directly and is certain that code injection is not possible, an!
may be added to pass unescaped HTML:
<div> Hello $!{"<b>World</b>"} </div>
Note
If necessary, the placeholder$
may be escaped with a\
:
<div> Placeholder example: <code>\${someValue}</code> </div>
The final way to pass attributes in Marko is with <@attribute-tags>
. It is technically possible to acheive everything that Marko has to offer without using these, but they provide an alternative method for passing complicated render bodies to components:
<my-select>
<@option>A</@option>
<@option>B</@option>
</my-select>
// identical to
<my-select
option=[
{ renderBody: "A" },
{ renderBody: "B" }
]
/>
In Marko, tags may also provide a return value to the template where they are referenced. The data type of these values is in many ways similar to that of JavaScript variables, but they also trigger updates to their dependencies every time their tag provides a new value. Tag variables will be explored further for native tags, core tags and custom tags, but the syntax is always as follows:
<my-tag/tagVariable />
While passing a renderBody
, tags may also specify parameters which can be passed back by the child template. These values are not the same as tag variables, because they are only accessible within the render body.
parent.marko
<child|a, b|>
${a}: ${b}
</child>
child.marko
<${renderBody}=[a, b]/>
Native tags in Marko are nearly identical to native tags in HTML, with a few additional features.
Event handlers such as onclick
are supported in native tags, but since they only are only able to receive strings it is not possible to attach them to any of Marko's features. Instead, Marko provides special event handlers on all native tags that are compiled as JavaScript. These are accessed via special camelCase onEvent
or kebab-case on-event
attributes.
Each of the following examples run the function body every time the "hover"
event is triggered on the button:
<button onHover=() => { /* runs on hover */ } />
<button on-hover=function() { /* runs on hover */ } />
<button onHover() { /* runs on hover */ } />
Note
Due to the nature of camelCase, theonEvent
option cannot be used to handle events that begin with capital letters. To handle anUppercase
event, useon-Uppercase
.
In addition to strings, the class
and style
attribute accept arrays or objects that will be converted to strings automatically by Marko. The output of each of these cases is identical:
// string
<div
class="a c"
style="display:block;margin-right:16px"
/>
// object
<div
class={ a: true, b: false, c: true}
style={ display: "block", color: false, marginRight: 16 }
/>
// array
<div
class=["a", null, { c: true }]
style=["display:block", null, { marginRight: 16 }]
/>
Every native tag provides a tag variable which acts as a getter function for a reference to its DOM object.
<div/myDiv>Hello World</div>
<div>
The previous element says ${myDiv().innerHTML}
</div>
This is only the case for built-in HTML elements. Conceptually, native tags can be thought of as having something like <return=() => ref />
under the hood.
Custom tags are single-file blocks of code that are used to separate an application into encapsulated, reusable components. Tags are discovered automatically based on file name, and treated as if they were built into Marko as HTML or core tags.
components/hello.marko
<h1>Hello World</h1>
page.marko
<html>
<body>
<hello/>
</body>
</html>
result.html
<html>
<body>
<h1>Hello World</h1>
</body>
</html>
input
Every component file has access to a global variable called input
, which acts as an object that contains its attributes.
components/hello.marko
<h1>Hello ${input.name}</h1>
page.marko
<html>
<body>
<hello name="Marko" />
</body>
</html>
result.html
<html>
<body>
<h1>Hello Marko</h1>
</body>
</html>
renderBody
The body content of each component is accessible in a component file as a member of the input
object called renderBody
:
fancy-border.marko
<div class="fancy-border">
<${input.renderBody}/>
</div>
Not sure if we should do alphabetical order or order of importance. This is roughly just the order I remembered them inLuke
<if>
/<else-if>
/<else>
These tags are used to conditionally render information. if
and else-if
will render their contents when their value
attribute is truthy, while else-if
and else
act as fallbacks for their previous conditional sibling.
<if value=arriving>
Hey there
</if>
<else-if=leaving>
Bye now
</else-if>
<else>
What’s up?
</else>
ProTip
See the default value shorthand for a more concise way of writingif
andelse-if
statements.
<for>
Just like JavaScript's for
loops, the Marko <for>
tag can iterate over arrays/array-likes, object properties and values, and ranges of numbers. The items being iterated over are received as tag parameters which may be used in the body content of the tag.
<for|item| of=["a", "b", "c"]>
${item}
</for>
<for|key, value| in={a: 1, b: 2, c: 3}>
${key}: ${value}
</for>
<for|num| to=5>${num}, </for>
// 0, 1, 2, 3, 4, 5,
<for|num| from=3 to=7>${num}, </for>
// 3, 4, 5, 6, 7,
<for|num| from=2 to=10 step=2>${num}, </for>
// 2, 4, 6, 8, 10,
This doesn't exist yet but maybe adding it to the docs will be enough to convince the team it's worth including in Marko 6 Luke
<for|num| until=5>${num}, </for>
// 0, 1, 2, 3, 4,
<for|num| from=3 until=7>${num}, </for>
// 3, 4, 5, 6,
<for|num| from=2 until=10 step=2>${num}, </for>
// 2, 4, 6, 8,
<let>
<let/x=4/>
<let/_x="HELLO" />
<let/x=_x valueChange(newValue) { _x = newValue.toUpperCase() } />
<const>
Declares a reactive, derived value which updates whenever any of its dependencies change.
<const/doubleX=x*2 />
Conceptually, under the hood the implementaton of the <const>
tag looks like this:
const.marko
<return=input.value/>
<effect>
Declares a function which re-runs when its dependencies change
<effect() {
console.log(doubleX);
// will log every time `doubleX` updates
}/>
<lifecycle>
Provides code which is only run on the client, throughout the lifecycle of a component. Consistent data can be stored in this
.
Fires when the component is first created
Identical to the <effect>
tag, but with access to its lifecycle's this
.
Fires before the component is destroyed
<lifecycle
onMount() {
// create utility
}
onUpdate() {
// use utility
}
onDestroy() {
// destroy utility
}
/>
<return>
my-const.marko
<return=input.value/>
<id>
Generates a unique id string which may be safely referenced across the server-client boundary.
<id/inputId />
<label for=inputId>...</label>
<input id=inputId />
<html-comment>
Normal HTML comments are automatically stripped from Marko files, these will not
<html-comment>Visible in the client</html-comment>
<define>
Almost the same as the <const>
tag, but it gives back a whole object instead of just value
. Can be used as an alternative to file-based tags
<define/fancyButton|{renderBody}|>
<button class="fancy"><${renderBody}/></button>
</define>
<fancyButton>Hello!</fancyButton>
Essentially, this is the tag's implementation under the hood:
define.marko
<return=input/>
If the first attribute in a tag doesn't have a name, Marko will automatically call it "value". Core tags take advantage of this, which is why these are equivalent:
<let/x value=1/>
<let/x=1/>
<let/count=0 />
<counter count:=count />
// transforms to
<counter count=count countChange(c) { count = c } />
counter.marko
<let/count:=input.count />
<button onClick() { count++ }>${count}</button>
// transforms to
<let/count=input.count valueChange=input.countChange />
class
and id
shorthandsMarko allows tag names to be written using a limited CSS selector syntax to include classes and ids:
<div#main.container />
// equivalent to
<div id="main" class="container" />