Mira
All docs

Templates

The .mira component format and its template syntax.

A .mira file is a single file component: optional frontmatter, a <template> block, and an optional <style> block. It compiles to HTML and CSS with no runtime.

---
title: Writing
---
<template>
<h1>{{ page.title }}</h1>
<ol>
  {#each collections.posts as post}
  <li><a href="{{ post.url }}">{{ post.title }}</a></li>
  {/each}
</ol>
</template>

<style>
ol { list-style: none; padding: 0; }
</style>

If a file has no <template> block, the whole body is the template.

Output

Write a value with double braces. Output is HTML escaped, so <, >, &, and quotes are always safe:

<h1>{{ page.title }}</h1>
<a href="{{ post.url }}">Read</a>

To write raw HTML, mark it unsafe. Every use is reported as a warning at build time so it stays a deliberate choice:

{{ unsafe page.trusted_html }}

Values are read by dotted paths. Arrays take numeric indexes and a length:

{{ collections.posts.0.title }}
{{ collections.posts.length }} posts

A missing path renders nothing. Strings print as is, numbers and booleans print as text, and lists or objects print as JSON.

Conditions

{#if post.description}
  <p>{{ post.description }}</p>
{:else}
  <p>No summary.</p>
{/if}

{#if !path} negates. A value is false when it is missing, null, false, 0, an empty string, or an empty list.

Loops

{#each collections.posts as post}
  <h2>{{ post.title }}</h2>
{/each}

Loop variables shadow outer names inside the loop.

Slots

<slot /> marks where wrapped content goes. In a layout it is the page; in a dynamic route it is the entry’s rendered Markdown.

Literal braces

Wrap text in {#raw} and {/raw} to output it exactly as written, which is how this site shows template syntax inside templates.

What templates can read

NameContents
sitetitle, description, url, and lang from config
pageThe page’s frontmatter, its url, and in dynamic routes the entry’s fields
entryIn dynamic routes: the entry’s frontmatter, slug, url, toc, prev, and next
collectionsEvery collection by name, as lists of entries
dataEvery file in data/ by name

Styles

A component’s <style> block is placed in the page cascade layer, and a layout’s in the layout layer. Both come after Mira’s own layers, so your styles win without !important. All CSS is inlined into the page; there is no stylesheet request.

Built in elements

The compiler expands a few elements at build time:

ElementBecomes
<mira-code lang="rust">…</mira-code>Highlighted code, indentation trimmed
<mira-search>A search box backed by the build’s index (Search)

Two attributes are compiled too: mira-morph (Shared elements) and mira-nav, which sets aria-current="page" on the link that matches the current page or its section.

Errors

Template mistakes fail the build with the file, the line, and a hint:

✕ {#each} is never closed

  routes/broken.mira:3
  › 3 {#each collections.posts as p}

  hint  add {/each} where the block should end