Template syntax
This document serves as a guide to Nue's HTML-based template syntax, which is shared across your Markdown extensions, server components, and client-side components.
Expressions
Text expressions
The simplest form of expressions is placed inside curly brackets:
<span>Text: { text }</span>
In this example, the brackets are replaced with the value of the text
property from the corresponding component instance. This value will update on the client side whenever the text
property changes, allowing for dynamic content rendering.
HTML expressions
Single brackets interpret the data as plain text, while double brackets are used to output HTML. Here's how { value: 'Hello, <b>World</b>!' }
is rendered:
<!-- Value is escaped: Hello, <b>World!</b> -->
<p>{ value }</p>
<!-- Value is rendered as HTML: Hello, <b>World!</b> -->
<p>{{ value }}</p>
<!-- Same as the above (Vue compatibility) -->
<p :html="value"/>
Be cautious when rendering HTML, as it can lead to XSS vulnerabilities if the content is user-generated.
Complex expressions
Nue supports the full power of JavaScript expressions within the curly brackets:
<p>{ message.split('').reverse().join('') }</p>
<p>{ ok ? '👍' : '😡' }</p>
An expression is a piece of code that evaluates to a value. Therefore, the following will NOT work:
<!-- This is a statement, not an expression -->
{ var a = 1 }
<!-- Use a ternary expression, not flow control -->
{ if (ok) { return message } }
Function calls
Expressions can also call instance methods:
<time :datetime="date.toISOString()">
{ prettyTime(date) }
<script>
prettyTime(date) {
return MY_CUTE_FORMAT.format(date);
}
</script>
</time>
Be mindful that functions called within expressions run every time a reactive component updates. They should not produce side effects, such as altering data or triggering asynchronous operations.
Attributes
You can pass values to your components using attributes. These values can be static or dynamic, and they can represent strings, numbers, arrays, or objects:
<!-- Static parameter -->
<media title="Introduction to Nue"/>
<!-- Dynamic parameter -->
<media :title="item.title" :class="item.class"/>
<!-- Array value -->
<media-list :items="items"/>
All attribute values are accessible inside the component:
<div ="media">
<h3>{ title }</h3>
</div>
Standard HTML attributes like id
, class
, style
, and data-*
remain on the element, while nonstandard attributes like :title
are removed after the value is passed to the component.
Interpolation
Nue supports both bracket syntax and string interpolation directly in attribute values:
<!-- Attribute value with brackets -->
<input type="{ type }"></input>
<!-- Vue-style binding also works -->
<input :type="type"></input>
<!-- String interpolation with brackets -->
<div class="gallery { class }">
<!-- A more complex example -->
<div style="background: url('{ background }')">
Boolean attributes
Nue automatically detects boolean attributes. In the following example, the disabled
attribute will be included if is_disabled
has a truthy value; otherwise, the attribute will be omitted.
<button :disabled="is_disabled">Press me</button>
Class attribute
Nue supports a special object notation to help render the class
attribute:
<label :class="field { is-active: isActive, has-error: hasError }"></label>
If both isActive
and hasError
are true, Nue will render:
<label class="field is-active has-error"></label>
You can combine the object notation with other bracket expressions:
<label :class="field { is-active: isActive } { getErrorClass() }"></label>
Class attribute merging
The parent class attribute is automatically merged with the child's class attribute. Suppose we have the following component:
<button ="my-button" class="btn">Click me</button>
If we mount it as follows:
<my-button class="large"/>
The final rendered button would combine both classes:
<button class="btn large">Click me</button>
Passing data with :bind
The bind
directive makes every object property directly accessible from the component. Instead of writing { data.title }
inside the component, you can simply write { title }
. This is particularly useful when looping through components.
<!-- Pass properties for the media object one by one -->
<media-object :for="item in items"
:title="item.title"
:desc="item.desc"
:img="item.img"/>
<!-- Or pass all properties at once with :bind -->
<media-object :for="item in items" :bind="item"/>
Rendering attributes with :attr
The attr
directive renders a DOM attribute for each property in an object. So the following component:
<p :attr="data">
<script>
data = {
title: 'My title',
alt: 'My alt',
};
</script>
</p>
Would be rendered as:
<p title="My title" alt="My alt"></p>
$attrs
property
All parent attributes are accessible via the $attrs
property. In this example, the nested input field will inherit all parent attributes.
<label ="field">
<h5>{ title }</h5>
<input :attr="$attrs">
</label>
When the above component is used as follows:
<field title="Email" type="email" placeholder="me@acme.org" required="true"/>
The rendered HTML would be:
<label>
<h5>Email</h5>
<input title="Email" type="email" placeholder="me@acme.org" required>
</label>
Control flow
:if condition
Use the :if
attribute to conditionally render a block. The block will only be rendered if the given expression returns a truthy value.
<figcaption :if="caption">{ caption }</figcaption>
:else condition
Use :else
to indicate an else block
for :if
.
<div>
<h1 :if="cool">I'm cool!</h1>
<h1 :else>I'm ordinary</h1>
<button ="cool = !cool">Toggle</button>
</div>
An :else
element must immediately follow an :if
or :else-if
element; otherwise, it will not be recognized.
:else-if condition
The :else-if
serves as an else if block
for :if
. It can be chained multiple times:
<b :if="type == 'A'">A</b>
<b :else-if="type == 'B'">B</b>
<b :else-if="type == 'C'">C</b>
<b :else>Not A/B/C</b>
Similar to :else
, a :else-if
element must immediately follow an :if
or :else-if
block.
:for loop
Nue uses the :for
attribute to iterate over lists and objects. Loops are defined with syntax like item in items
, where items
is the data array and item
is the element being iterated:
<ul>
<li :for="item in items">
{ item.lang } = { item.text }
</li>
<script>
items = [
{ lang: 'en', text: 'Hello' },
{ lang: 'es', text: 'Hola' },
{ lang: 'it', text: 'Ciao' },
{ lang: 'fi', text: 'Moi' }
];
</script>
</ul>
Inside the loop, template expressions have access to the item being looped, as well as all parent properties. Additionally, :for
supports an optional second alias for the index of the looped item:
<li :for="(item, index) in items">
{ index }: { item.text }
</li>
You can use destructuring for the item variable, similar to destructuring function arguments:
<li :for="{ lang, text } in items">
{ lang } = { text }
</li>
Destructuring and the index variable can be used together:
<li :for="({ text }, index) in items">
{ text } { index }
</li>
Loops can be nested, and each :for
scope has access to all parent scopes:
<li :for="item in items">
<p :for="child in item.children">
{ item
.text } { child.text }
</p>
</li>
You can also use of
as the delimiter instead of in
, so that it is closer to JavaScript's syntax for iterators:
<div :for="item of items"></div>
Object loops
You can loop through object values using the standard Object.entries()
method:
<ul>
<li :for="[lang, text] in Object.entries(items)">
{ lang } = { text }
</li>
<script>
items = {
en: 'Hello',
es: 'Hola',
it: 'Ciao',
fi: 'Moi'
};
</script>
</ul>
You can provide an alias for the index variable as the third argument:
<ul>
<li :for="[lang, text, index] in Object.entries(items)">
{ index } / { lang } = { text }
</li>
</ul>
Conditional loops
When :if
and :for
exist in the same node, :if
has a higher priority. This means the :if
condition is evaluated first.
<li :for="todo in todos" :if="todos">
{{ todo.name }}
</li>
Use the standard hidden
property to conditionally hide elements inside a loop:
<li :for="todo in todos" :hidden="!todo.is_complete">
{{ todo.name }}
</li>
Component loops
Components can also be looped:
<my-component :for="item in items"/>
You can pass the iterated data to the component with attributes:
<my-component
:for="(item, index) in items"
:item="item"
:index="index"
/>
Alternatively, you can use the :bind
attribute to pass all the data at once:
<my-component :for="item in items" :bind="item"/>
The bind
attribute makes the item properties accessible directly to the component. So instead of { item.title }
, you can write { title }
inside the component.