Skip to content
Damask

Brace tags

Printing, statements, raw output, control flow, imports and comments.


Every construct the template language adds lives inside { … }, and the contents are a Rust block. Braces balance, and string and char literals are stepped over whole — so a } inside a Rust string does not end the tag.

{ expr } — print

Prints the value, escaped by the active renderer.

Damask
<p>{self.name}</p>

Because the tag is a block, a statement runs and prints nothing, and the last expression is what prints:

Damask
{let count = self.rows.len()}
{2 + 3; 10}                  <!-- prints 10 -->

A tag prints nothing when it ends in ;, or when it opens with one of these keywords:

let · const · use · fn · static · type · struct · enum · trait · impl · mod

An item declared this way is an ordinary Rust item in the enclosing scope, so a helper fn may be defined in the template that uses it. A binding is let, and the trailing ; is optional — {let x = 1} and {let x = 1;} are the same.

A literal brace has to be written as an expression: {"{"}.

{@html expr} — print raw

No escaping. For content you produced yourself, or that is already escaped.

Damask
<div class="prose">{@html self.body}</div>

{@render expr} — render content

Renders anything implementing Render: a snippet, a Fragment, a slot lookup, an Option of any of them, or a component value built in Rust. The expression is borrowed, not moved, and the content writes into the same renderer.

Damask
{#snippet chip(label)}<span class="chip">{label}</span>{/snippet}
{@render chip("all")}
{@render slots.get("actions")}

A component written as a tag<Chip label="all"/> — is the usual form; {@render} is for content that is already a value.

{#if} / {:else if} / {:else} / {/if}

Damask
{#if self.admin}
  <span class="badge">admin</span>
{:else if self.pending}
  <span class="badge badge--muted">pending</span>
{:else}
  <span class="badge badge--plain">member</span>
{/if}

The condition is spliced into a Rust if, so if let works:

Damask
{#if let Some(error) = &self.error}<p class="error">{error}</p>{/if}

{#for pat in E} / {/for}

A Rust for loop: pat is any pattern, E any IntoIterator expression — usually &self.items. The header is split on the first in, and both halves must be non-empty. There is no bespoke loop syntax to learn; it lowers to the for you already write.

Damask
{#for item in &self.items}<li>{item.label}</li>{/for}

The pattern is real Rust, so a tuple destructures and an index comes from .enumerate() — exactly as in a hand-written loop.

Damask
{#for (i, chapter) in self.chapters.iter().enumerate()}<li>{i + 1}. {chapter.title}</li>{/for}
{#for (key, value) in &self.pairs}<dt>{key}</dt><dd>{value}</dd>{/for}
Form Lowers to
{#for pat in E} for pat in E { … }

{#snippet name(params)} / {/snippet}

Defines a reusable fragment. The name is everything before the first (, the parameters everything up to the last ); the parameter list may be empty. Snippets are let bindings, so a snippet must be defined before it is rendered and goes out of scope with the element it was defined in.

Damask
{#snippet row(label, value)}<tr><th>{label}</th><td>{value}</td></tr>{/snippet}

See Snippets and fragments for what the two forms lower to.

{use path}

An ordinary Rust use, scoped to the enclosing element. Import components, functions, enums, anything.

Damask
<section>
  {use crate::ui::{Badge, Dot}}
  <Badge tone={self.tone}/>
</section>
<!-- Badge is out of scope here -->

At the top of a template, before any element, the import covers the whole file. Component and Render are already in scope in every template, so child.render() needs no import.

{# … #} — comment

Does not reach the output, unlike <!-- … -->. The whitespace after {# is what tells it from a block tag, so {#if} is never mistaken for one. Braces and tags inside a comment are prose, not structure, and the closing #} is required.

Damask
{# The bottom margin belongs to the header, not the caller. #}

Restrictions

Control flow cannot appear in attribute position:

Damask
<input {#if self.locked}disabled{/if}>   <!-- parse error -->

Attribute names are static — there is no data-{key}=. Express a conditional attribute through a bool or Option value; for a map of data-*, hand the whole map to {@attrs(…)}; for names of any other shape, build the run in Rust and splice it with {...expr}.

An empty tag ({}), an empty expression in {@html}, {@render}, {#if} or an attribute value, an unknown directive ({@foo}), an unknown block ({#foo}) and an unknown clause ({:foo}) are all errors at build time, reported against the template.