Templates
Templates define the HTML structure for rendered pages using the Tera template engine.
Template Location
Templates are stored in the templates/ directory:
templates/
├── base.html # Base template with common structure
├── page.html # Single page template
├── section.html # Section/list template (e.g., blog)
├── tags.html # Tag listing page (all tags)
├── tags_term.html # Individual tag page (e.g., /tags/rust/)
├── categories.html # Category listing page (all categories)
├── categories_term.html # Individual category page (e.g., /categories/tutorial/)
├── series.html # Series listing page (all series)
├── series_term.html # Individual series page (e.g., /series/learning-rust/)
└── 404.html # Not found page
Template Engine
Taxus uses Tera, a Jinja2-like template engine for Rust:
- Variables:
{{ variable }}syntax - Filters:
{{ content | safe }}for unescaped HTML - Conditionals:
{% if condition %}...{% endif %} - Loops:
{% for item in items %}...{% endfor %} - Template Inheritance:
{% extends "base.html" %}and{% block name %} - Includes:
{% include "partial.html" %}
Base Template
The base template defines the common HTML structure:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}{{ site.name }}{% endblock %}</title>
{% if page.permalink %}
<link rel="canonical" href="{{ page.permalink }}" />
<meta property="og:url" content="{{ page.permalink }}" />
{% endif %}
<link rel="stylesheet" href="/css/main.css" />
<link rel="icon" href="/static/favicon.png" />
</head>
<body>
<header>
<nav>
<a href="/">Home</a>
<a href="/about/">About</a>
</nav>
</header>
<main>{% block content %}{% endblock %}</main>
<footer>
<p>© {{ now.year }} {{ site.author | default(value="") }}</p>
</footer>
<script src="/static/scripts.js"></script>
</body>
</html>
Page Template
Page templates extend the base template:
{% extends "base.html" %}
{% block title %}{{ page.title }} - {{ site.name }}{% endblock %}
{% block content %}
<article>
{% if page.hero %}
<picture>
<source srcset="{{ page.hero.srcset | safe }}" type="{{ page.hero.mime_type }}">
<img src="{{ page.hero.src | safe }}" alt="{{ page.hero.alt }}"
width="{{ page.hero.width }}" height="{{ page.hero.height }}"
loading="eager" decoding="async">
</picture>
{% endif %}
<h1>{{ page.title }}</h1>
{% if page.description %}
<p class="description">{{ page.description }}</p>
{% endif %}
{% if page.date %}
<time datetime="{{ page.date }}">{{ page.date }}</time>
{% endif %}
{% if page.tags %}
<div class="tags">
{% for tag in page.tags %}
<a href="/tags/{{ tag | term_slug }}/">{{ tag }}</a>
{% endfor %}
</div>
{% endif %}
<div class="content">{{ page.content | safe }}</div>
</article>
{% endblock %}
Section Template
Section templates render lists of pages:
{% extends "base.html" %}
{% block title %}{{ section.title }} - {{ site.name }}{% endblock %}
{% block content %}
<section>
<h1>{{ section.title }}</h1>
{% if section.description %}
<p class="description">{{ section.description }}</p>
{% endif %}
{% if section.content %}
<div class="section-content">{{ section.content | safe }}</div>
{% endif %}
<ul class="page-list">
{% for page in section.pages %}
<li>
<a href="{{ page.path }}">
<span class="title">{{ page.title }}</span>
{% if page.date %}
<time datetime="{{ page.date }}">{{ page.date }}</time>
{% endif %}
<span class="reading-time">{{ page.reading_time }} min read</span>
</a>
{% if page.summary %}
<p class="summary">{{ page.summary }}</p>
{% endif %}
</li>
{% endfor %}
</ul>
{% if section.pagination %}
<nav class="pagination">
{% if section.pagination.prev %}
<a href="{{ section.pagination.prev }}">← Previous</a>
{% endif %}
<span>Page {{ section.pagination.current }} of {{ section.pagination.total }}</span>
{% if section.pagination.next %}
<a href="{{ section.pagination.next }}">Next →</a>
{% endif %}
</nav>
{% endif %}
</section>
{% endblock %}
Taxonomy Templates
Taxus generates taxonomy listing and term pages when the corresponding templates exist. The scaffold (taxus init) creates all six templates automatically.
Taxonomy List Templates
List templates render all terms for a taxonomy kind:
tags.html—/tags/categories.html—/categories/series.html—/series/
Each receives extra.taxonomy with:
| Variable | Type | Description |
|---|---|---|
extra.taxonomy.kind | String | Taxonomy kind: "Tags", "Categories", or "Series" |
extra.taxonomy.path | String | URL path (e.g., "/tags/") |
extra.taxonomy.terms | Array | List of term contexts |
Example tags.html:
{% extends "base.html" %}
{% block title %}Tags - {{ site.name }}{% endblock %}
{% block content %}
<section>
<h1>Tags</h1>
{% if extra.taxonomy.terms %}
<ul>
{% for term in extra.taxonomy.terms %}
<li>
<a href="{{ term.path }}">{{ term.name }} ({{ term.page_count }})</a>
</li>
{% endfor %}
</ul>
{% endif %}
</section>
{% endblock %}
Taxonomy Term Templates
Term templates render pages for a specific term:
tags_term.html—/tags/rust/categories_term.html—/categories/tutorial/series_term.html—/series/learning-rust/
Each receives extra.taxonomy with:
| Variable | Type | Description |
|---|---|---|
extra.taxonomy.kind | String | Taxonomy kind: "Tags", "Categories", or "Series" |
extra.taxonomy.name | String | Display name (e.g., "Rust") |
extra.taxonomy.slug | String | URL-safe slug (e.g., "rust") |
extra.taxonomy.path | String | URL path (e.g., "/tags/rust/") |
extra.taxonomy.page_count | Number | Number of pages with this term |
extra.taxonomy.pages | Array | List of page objects with title, path, description, etc. |
Example tags_term.html:
{% extends "base.html" %}
{% block title %}Tag: {{ extra.taxonomy.name }} - {{ site.name }}{% endblock %}
{% block content %}
<section>
<h1>Tagged "{{ extra.taxonomy.name }}"</h1>
<p>{{ extra.taxonomy.page_count }} post(s)</p>
<ul>
{% for page in extra.taxonomy.pages %}
<li><a href="{{ page.path }}">{{ page.title }}</a></li>
{% endfor %}
</ul>
</section>
{% endblock %}
Term Context in List Templates
When iterating extra.taxonomy.terms, each term has:
| Variable | Type | Description |
|---|---|---|
term.name | String | Display name |
term.slug | String | URL-safe slug |
term.path | String | URL path |
term.page_count | Number | Number of pages |
Available Variables
Site Context
| Variable | Type | Description |
|---|---|---|
site.name | String | Site name from configuration |
site.base_url | String | Base URL from configuration |
site.description | String? | Optional site description |
site.author | String? | Optional site author |
Page Context
| Variable | Type | Description |
|---|---|---|
page.title | String | Page title from frontmatter |
page.description | String? | Optional page description |
page.tagline | String? | Optional tagline from frontmatter |
page.path | String | URL path (e.g., /about/), derived from the page's node path |
page.permalink | String | Absolute URL (e.g., https://example.com/about/) |
page.content | String | Rendered HTML content |
page.raw_content | String | Raw markdown content |
page.date | String? | Publication date (ISO 8601) |
page.draft | Boolean | Whether page is a draft |
page.summary | String | Summary/excerpt for the page |
page.word_count | Number | Word count |
page.reading_time | Number | Estimated reading time in minutes |
page.toc | Array | Table of contents: entries with level, text, id, children (absent when the page has no headings) |
page.weight | Number | Frontmatter weight (0 when unset); sections with sort_by = "weight" list pages in this order |
page.tags | Array | Tags for the page |
page.categories | Array | Categories for the page |
page.series | String? | Series name |
page.hero | Object? | Hero image context (see below) |
Hero Image Context
When a page has hero_image in its frontmatter, page.hero contains:
| Variable | Type | Description |
|---|---|---|
page.hero.src | String | Fallback <img> src (middle variant) |
page.hero.srcset | String | Full srcset string for <source> |
page.hero.width | Number | Original image width |
page.hero.height | Number | Original image height |
page.hero.alt | String | Alt text (from hero_alt, or page title) |
page.hero.mime_type | String | MIME type (e.g., "image/webp") |
Example usage:
{% if page.hero %}
<picture>
<source srcset="{{ page.hero.srcset | safe }}" type="{{ page.hero.mime_type }}">
<img src="{{ page.hero.src | safe }}" alt="{{ page.hero.alt }}"
width="{{ page.hero.width }}" height="{{ page.hero.height }}"
loading="eager" decoding="async">
</picture>
{% endif %}
See Images for the complete guide.
Section Context
| Variable | Type | Description |
|---|---|---|
section.title | String | Section title |
section.description | String? | Optional section description |
section.path | String | Section URL path |
section.permalink | String | Absolute URL of the section |
section.content | String? | Section HTML content |
section.toc | Array | Table of contents of the section's _index.md |
section.pages | Array | The section's direct child pages, plus the direct pages of any pages_from sections, sorted by sort_by |
section.subsections | Array | Direct child sections, in slug order; each has title, description, path, permalink |
section.pagination | Object? | Pagination information |
Pagination Context
| Variable | Type | Description |
|---|---|---|
section.pagination.current | Number | Current page (1-indexed) |
section.pagination.total | Number | Total pages |
section.pagination.per_page | Number | Items per page |
section.pagination.total_items | Number | Total items across all pages |
section.pagination.prev | String? | URL to previous page |
section.pagination.next | String? | URL to next page |
section.pagination.first | String | URL to first page |
section.pagination.last | String | URL to last page |
Current Date
| Variable | Type | Description |
|---|---|---|
now.year | Number | Current year (e.g., 2024) |
Useful for copyright notices: © {{ now.year }}
Extra Variables
Custom variables from frontmatter extra field:
+++
title = "My Page"
[extra]
author = "John Doe"
custom = "value"
+++
Access in templates:
<p>Author: {{ extra.author }}</p>
<p>Custom: {{ extra.custom }}</p>
Template Inheritance
Templates can extend other templates:
base.html:
<html>
<head>{% block head %}{% endblock %}</head>
<body>{% block body %}{% endblock %}</body>
</html>
page.html:
{% extends "base.html" %}
{% block head %}
<title>{{ page.title }}</title>
{% endblock %}
{% block body %}
<h1>{{ page.title }}</h1>
{{ page.content | safe }}
{% endblock %}
Tree Functions
Templates can reach any part of the Site Tree, not just the section being rendered (#69). This is how a home page lists recent posts it does not own:
{% set blog = get_section(path="blog") %}
<ul>
{% for page in blog.pages | slice(end=5) %}
<li><a href="{{ page.path }}">{{ page.title }}</a></li>
{% endfor %}
</ul>
{% for sub in section.subsections %}
<a href="{{ sub.path }}">{{ sub.title }}</a>
{% endfor %}
{% set about = get_page(path="about") %}
<a href="{{ about.path }}">{{ about.title }}</a>
| Function | Returns |
|---|---|
get_section(path="blog") | The section as a section object: title, description, path, permalink, content, toc, pages (sorted by that section's sort_by, including its pages_from), subsections. pagination is never set — slicing belongs to the section's own render |
get_page(path="blog/my-post") | The page as a page object, with the same fields as page |
Paths are content-relative tree paths, the same form pages_from uses:
blog, blog/2026, blog/my-post. Leading and trailing slashes are
ignored, a section can also be named by its index file (blog/_index.md),
a page by its content file (blog/2026-04-06-my-post.md), and the root is
"", / or _index.md. A directory without an _index.md is still a
section (with an empty title and no content). Drafts are absent unless the
build includes them. A path that names nothing fails the render, so a typo
stops the build instead of producing an empty list.
Filters
Commonly used filters:
| Filter | Description |
|---|---|
safe | Output without HTML escaping |
default(value="...") | Provide default value |
upper | Convert to uppercase |
lower | Convert to lowercase |
trim | Remove leading/trailing whitespace |
first | Get first element of array |
last | Get last element of array |
length | Get length of string/array |
join(sep=", ") | Join array with separator |
slugify | Convert to URL-safe slug (ASCII) |
term_slug | Taxonomy term slug: keeps non-ASCII letters (Café → café); use for tag/category/series links |
Custom Templates
Pages can specify custom templates in frontmatter:
+++
title = "Special Page"
template = "custom.html"
+++
This page uses custom.html instead of page.html.
Island Components
Use the island() function to embed interactive Yew components:
{% block content %}
{{ page.content | safe }}
{{ island(component="Counter", initial=5) | safe }}
{% endblock %}
Important: Always use | safe after island() to prevent HTML escaping.
See Islands Architecture for the complete guide.