Templating Guide

Accent CMS uses MiniJinja for templating, providing Jinja2-style syntax with template inheritance, filters, functions, and control flow. Templates live in your theme’s templates/ directory and have access to page content, site configuration, and a rich set of helpers.

Template Basics

Templates use the standard Jinja2 syntax:

  • Variables: {{ variable }} outputs a value
  • Tags: {% tag %} executes logic (if/for/block/extends)
  • Comments: {# comment #} are stripped from output

Template inheritance lets you define a base layout and override blocks in child templates:

{# base.html.jinja #}
<html>
<body>
    {% block content %}{% endblock %}
</body>
</html>

{# default.html.jinja #}
{% extends "base.html.jinja" %}
{% block content %}
    <h1>{{ page.title }}</h1>
    {{ page.content | safe }}
{% endblock %}

Topics

This guide is organized into focused sub-pages. Each covers a specific aspect of Accent CMS templating:

Template Syntax

The base MiniJinja language Accent runs - delimiters, variables and expressions, control flow, assignments, template inheritance, macros, whitespace control, and auto-escaping - plus the exact engine version and features Accent ships and the built-in filter/test lists. Start here if you are new to Jinja-style templating.

Filters and Functions

Custom filters (date, truncate, slugify) and functions (now(), media(), url(), debug()) for transforming data and generating dynamic content. Also covers the dev context and accent build information.

Page Context

Page hierarchy (parent, children, siblings, breadcrumbs, prev/next), table of contents, computed metadata (word count, reading time), taxonomy/tags, page metadata properties, and auto-excerpt generation.

Collections and Pagination

Content collections via frontmatter, filtering and sorting, automatic pagination with page navigation, static build output, and named content relationships between pages.

Media in Templates

Page media assets (page.media), type filters (filter_images, filter_videos, filter_documents), image processing with responsive_image() and thumbnail(), video embedding with video() and video_embed().

Modular Pages

Composing a single page from multiple _-prefixed subdirectories, each with its own frontmatter, content, and custom fields. Ideal for landing pages and feature showcases.

Structured Sections

Defining multiple named content regions within a single markdown file using ---section: name--- markers. A lightweight alternative to modular pages for simple multi-region layouts.

Social Sharing and SEO Metadata

Open Graph, Twitter Cards, build-safe canonical links, and the open meta map. The absolute_url() helper, the resolved page.description/page.social_image/page.meta_tags primitives, and how to add any platform tag from config without touching a template.

Custom Frontmatter Fields

Any YAML key in the frontmatter that is not a built-in field (title, date, author, lead, tags, published, template, menu, relations) is passed through to templates via page.custom.

Markdown frontmatter:

---
title: My Product
hero_image: /images/hero.jpg
featured: true
social:
  twitter: "@myhandle"
  github: "myrepo"
---

Template usage:

{# Access custom scalar fields #}
<img src="{{ page.custom.hero_image | safe }}" alt="{{ page.title }}">

{% if page.custom.featured %}
<span class="badge">Featured</span>
{% endif %}

{# Access nested custom fields #}
<a href="https://twitter.com/{{ page.custom.social.twitter }}">Twitter</a>

{# Check if a custom field exists #}
{% if page.custom.hero_image is defined %}
<div class="hero" style="background-image: url('{{ page.custom.hero_image | safe }}')"></div>
{% endif %}

Custom fields support strings, numbers, booleans, lists, and nested objects.

Combining Filters and Functions

Filters and functions can be combined:

{# Truncated debug output #}
{{ debug(page) | truncate(200) }}

{# Slugified title for anchor #}
<a name="{{ page.title | slugify }}"></a>

Built-in MiniJinja Features

In addition to the custom filters and functions above, MiniJinja provides:

Built-in Filters:

  • safe - Mark HTML as safe (no escaping)
  • escape / e - HTML escape
  • lower / upper - Case conversion
  • trim - Remove whitespace
  • length - Get collection length
  • first / last - Get first/last item
  • join - Join list with separator
  • default - Provide fallback value
  • sort / reverse - List ordering

Built-in Tests:

  • defined / undefined
  • none
  • odd / even
  • eq / ne / lt / le / gt / ge

Control Flow:

  • {% if %} / {% elif %} / {% else %}
  • {% for item in list %}
  • {% include "partial.html.jinja" %}
  • {% extends "base.html.jinja" %} / {% block name %}

Quick Reference

TypeSyntaxExample
Filter{{ value | filter }}{{ title | slugify }}
Filter with arg{{ value | filter(arg) }}{{ text | truncate(50) }}
Function{{ function() }}{{ now() }}
Function with arg{{ function(arg) }}{{ debug(page) }}
Property access{{ object.property }}{{ now().year }}
Chained filters{{ value | f1 | f2 }}{{ title | lower | slugify }}

Markdown Templates

When the Markdown View feature is enabled with default_format: markdown, Accent CMS resolves *.md.jinja templates instead of *.html.jinja. These templates output structured markdown using the same template engine, variables, and inheritance system as HTML templates.

Template Resolution

HTML mode:     {page.template}.html.jinja  ->  default.html.jinja  (fallback)
Markdown mode: {page.template}.md.jinja    ->  default.md.jinja    (fallback)

If no .md.jinja template exists at all, the raw page content is served without template wrapping.

Template Variables

In markdown mode, page.content contains the raw markdown body (not rendered HTML). All other template variables remain unchanged:

VariableMarkdown ModeHTML Mode
page.contentRaw markdownRendered HTML
page.title, page.url, etc.SameSame
page.children, page.tocSameSame
page.custom.*SameSame

In HTML mode with Markdown View enabled, two additional variables are available:

VariableTypeDescription
page.markdown_urlstringURL to view the raw markdown (e.g., /docs/guide.md)
markdown_view_enabledboolWhether the feature is enabled site-wide

Example Markdown Template

{# default.md.jinja #}
{% extends "base.md.jinja" %}

{% block content %}
# {{ page.title }}

{% if page.date %}*{{ page.date }}*{% endif %}

{{ page.content }}

{% if page.tags %}
**Tags:** {% for tag in page.tags %}[{{ tag }}](/tags/{{ tag }}){% if not loop.last %}, {% endif %}{% endfor %}
{% endif %}
{% endblock %}

In HTML mode, include the partial to show a link to the raw markdown source:

{% include "partials/markdown-link.html.jinja" %}

The partial renders a link to page.markdown_url only when markdown_view_enabled is true.

Custom Error Pages

Accent CMS supports themed error pages through the template system. When an error occurs (e.g., a 404 Not Found), Accent CMS resolves the error page through this chain:

  1. Content page: content/error/404.md (or content/error/404.{lang}.md for i18n)
  2. Specific template: error/404.html.jinja
  3. Generic template: error.html.jinja
  4. Built-in: Plain text or dev error page (if no templates exist)

Error Context

Error templates receive an error context object with these fields:

FieldTypeDescription
error.statusnumberHTTP status code (e.g., 404, 500)
error.titlestringHuman-readable title (e.g., “Page Not Found”)
error.messagestringError description
error.pathstringThe requested URL path
error.languagestringDetected language code

Example Error Template

{% extends "base.html.jinja" %}

{% block content %}
<div class="error-page">
  <h1>{{ error.status }}</h1>
  <h2>{{ error.title }}</h2>
  {% if page.content %}
    {{ page.content | safe }}
  {% else %}
    <p>The page <code>{{ error.path }}</code> could not be found.</p>
  {% endif %}
  <a href="/">Back to home</a>
</div>
{% endblock %}

Error Content Pages

Create a content page at content/error/404.md to provide custom body text:

---
title: Page Not Found
template: error/404
menu:
  visible: false
---

The page you are looking for does not exist or has been moved.

The rendered markdown appears as page.content in the error template. Set menu.visible: false to keep error pages out of navigation.

The default theme ships a print stylesheet at themes/default/assets/css/print.css, linked from base.html.jinja with media="print":

<link rel="stylesheet" media="print"
    href="{{ cdn_url('/' ~ (('theme/assets/css/print.css') | fingerprint)) }}">

Because it is behind media="print", the browser fetches it at low priority and applies it only when the page is printed or saved as a PDF. It can never affect the screen render.

The sheet is deliberately not listed under assets.css in theme.yaml – that list produces screen <link> tags. Keep the print sheet as its own <link> so the media attribute survives.

What it does

AreaBehaviour in print
ChromeHides the header and nav, the sidebar and “On this page” rail, the search box and overlay, prev/next and pagination navigation, the theme toggle, version and language switchers, and the dev debug panel
ColourRedefines the design tokens to black-on-white, including dark mode, and drops background fills so a page does not flood the printer with toner
LayoutExpands the article to the full print width once the side rails are gone, including the .prose reading-measure cap the utility layer applies (65ch is right for a wide screen, but on A4 it leaves roughly a third of the sheet empty)
CodeWraps long lines instead of clipping them, and flattens syntax-highlight colours to black – highlight themes are usually dark, and their pale glyphs would print near-invisible
FragmentationKeeps headings with the text that follows, and keeps code blocks, tables, quotes, figures, and admonitions whole when they fit on a page
LinksAppends the destination of external links, so [the repo](https://example.com) prints as “the repo (https://example.com)”. In-page anchors and image links are left alone

A block taller than a single page still breaks across pages – that is correct. The flip side of keeping a large table whole is that it moves to the next sheet when it does not fit in the space left, which can leave a gap at the bottom of the previous one.

Overriding page size and margins

Page geometry lives in the @page rule. A4 with 18mm/16mm margins is the default; override it in your own theme’s stylesheet to switch to US Letter or different margins:

@page {
  size: letter;
  margin: 0.75in 0.6in;
}

@page :first {
  margin-top: 1in;
}

To suppress the printed URL after a specific link – an icon or image link, say – give it the no-print-url class:

<a href="https://example.com" class="no-print-url">Docs</a>

Running headers, generated page numbers, and a page-numbered table of contents are not part of this: browsers do not honour @page margin boxes or counter(page) without a JavaScript paginator, so declaring them would have no effect.

Here’s a complete example showing filters and functions in a footer:

<footer>
    <p>&copy; 2025-{{ now().year }} {{ site.name }}</p>
    <p>Page: {{ page.title | slugify }}</p>
    <p>Tags: {% for tag in page.tags %}{{ tag }}{% if not loop.last %}, {% endif %}{% endfor %}</p>
</footer>

In this section

  • Template Syntax

    The base MiniJinja language Accent runs - variables, expressions, control flow, inheritance, macros, whitespace control, and auto-escaping - plus the exact engine version and features Accent ships.

  • Filters and Functions

    Transform data with filters and generate dynamic content with functions like now(), media(), recent_documents(), and debug().

  • Page Context

    Access page hierarchy, breadcrumbs, siblings, taxonomy, computed metadata, and auto-excerpts in templates.

  • Collections and Pagination

    Define content collections with filtering and sorting, paginate results, and create named page relationships.

  • Diagrams in Templates

    Render Mermaid (and other) diagrams from Jinja templates and frontmatter using the diagram() template function.

  • Media in Templates

    Work with page media assets, image processing, responsive images, thumbnails, and video embedding in templates.

  • Modular Pages

    Compose a single page from multiple content sections using modular subdirectories with independent frontmatter.

  • Structured Sections

    Define multiple named content regions within a single markdown file for multi-region page layouts.

  • Social Sharing and SEO Metadata

    Open Graph, Twitter Cards, build-safe canonical links, and the open meta map -- every page shareable by default with zero per-page authoring.