Theming Guide
Learn how to create custom themes for your Accent CMS site.
Styling for Markdown-Based Content
Since Accent CMS renders markdown to HTML, the best CSS frameworks are those that style semantic HTML elements directly. Classless CSS frameworks work exceptionally well because they require no special markup - your markdown automatically looks great.
Key considerations:
- Semantic HTML elements (
<article>,<header>,<nav>,<main>,<footer>) are styled automatically - Typography is handled out of the box - headings, paragraphs, lists, blockquotes
- Code blocks with syntax highlighting work with most frameworks
- Tables and forms render properly without custom classes
- Dark mode support via
prefers-color-schemeor data attributes
Avoid frameworks that require heavy class annotations (like Tailwind or Bootstrap utilities) since markdown output cannot include arbitrary classes.
Available Themes
accent init installs the simple starter theme (and the default theme powers the documentation site). Starter templates with ready-made themes are available via accent init --list.
| Theme | Framework | Best For |
|---|---|---|
| simple | Simple.css | Clean, classless styling with dark mode (installed by accent init) |
| default | Custom CSS | Starting point, minimal styling (powers the docs site) |
Beyond the bundled themes, published themes install straight from the ecosystem registry – signature- and checksum-verified:
accent theme search dark # find a theme by name, description, or tag accent theme list --remote # or browse everything the registry publishes accent theme install agentic # install one accent theme outdated # see which installed themes are behind accent theme update agentic # move one to the latest version
Installing does not activate: set theme.name in config.yaml to switch to
it.
A theme is yours to edit once installed – which is exactly why
accent theme update will not silently overwrite your work. If any installed
file was modified, added, or removed since install, the update refuses and
lists each difference; re-run with --force to discard the listed changes,
or copy them somewhere safe first. See The Hub for the full
theme, plugin, and template distribution story, including how retracted
versions are handled.
Classless Frameworks (Recommended)
Simple and Pico themes use classless CSS frameworks that style HTML elements directly. Your markdown content renders beautifully without any template modifications:
- Automatic dark/light mode based on system preference
- Responsive typography and spacing
- Styled tables, code blocks, and forms
- Minimal CSS footprint
Traditional Frameworks
The Cinder theme demonstrates using a class-based framework (Bootstrap). This approach requires more template customization but offers more control over component styling.
Lightweight Grid Frameworks
The Skeleton theme uses a minimal CSS boilerplate (~400 lines) that provides:
- 12-column responsive grid system
- Base typography for markdown elements
- Styled buttons, forms, tables, and code blocks
- Raleway font for clean typography
How Skeleton maps to markdown:
| Markdown | HTML Output | Skeleton Styling |
|---|---|---|
# Heading | <h1> | 4-5rem, weight 300, letter-spacing |
**bold** | <strong> | Inherited |
[link](url) | <a> | Cyan color (#1EAEDB) |
`code` | <code> | Gray background, rounded corners |
```code | <pre><code> | Block display, padding |
- list | <ul><li> | Circle markers, proper spacing |
> quote | <blockquote> | 2.5rem margin |
| ` | table | ` |
Using the grid in templates:
Skeleton’s grid is available for custom layouts in templates:
<div class="row">
<div class="eight columns">
{{ page.content | safe }}
</div>
<div class="four columns">
<aside>Sidebar content</aside>
</div>
</div>
Grid classes: .one.column through .twelve.columns, plus .one-half, .one-third, .two-thirds.
Theme Structure
A theme consists of templates, assets, and a configuration file:
site/themes/my-theme/
├── theme.yaml # Theme configuration
├── shortcodes/ # Components: a manifest and a template each
├── templates/ # Jinja2 templates
│ ├── base.html.jinja
│ ├── default.html.jinja
│ └── blog.html.jinja
└── assets/ # Static files
├── css/
│ └── style.css
├── js/
│ └── main.js
└── images/
Theme Configuration
The theme.yaml file defines your theme:
name: My Theme description: A custom theme for my site author: Your Name version: 1.0.0 assets: css: - css/style.css js: - js/main.js
Site-wide values with site.extra
For values a theme needs site-wide – an announcement bar, a social handle, a feature flag – put them under site.extra in config.yaml and read them as site.extra.* in any template:
site: extra: newsbar: enabled: true message: "No free slots at the moment."
{% if site.extra.newsbar.enabled %}
<div class="newsbar">{{ site.extra.newsbar.message }}</div>
{% endif %}
site.extra is an empty mapping when the key is absent, so {% if site.extra.x %} is always safe, and it reloads with the config (SIGHUP, or saving in dev). For multilingual values, nest under the language code (site.extra.newsbar.message.de) and index by site.current_language. Use site.extra for theme settings, get_env() for secrets and per-environment values, and load_data() for user-generated content.
Components and the canonical set
A theme’s components live in shortcodes/, a YAML manifest and a template for
each; Declared components covers the format.
Every theme implements the
canonical components:
callout, accordion, button, card, tag, image, breadcrumb, tabs,
and tab, with their canonical attribute names, plus styles for the built-in
table. Implementing them is what lets a site switch to your theme without its
pages breaking.
accent validate checks this, and needs no content to do it:
accent validate --templates
Each canonical component also carries the markup landmarks: data-canonical on
its root element and data-part on its parts, as the
markup landmarks table
lists. The classes and the structure around them stay yours. accent validate
does not check the landmarks, so compare your templates against that table; the
default theme’s templates carry every one.
A theme whose CSS framework has no tabs starts from the default theme’s
assets/js/tabs.js, which switches panels, supports keyboard navigation, and
remembers the selection.
A theme that provides no components declares it, and the check does not apply:
# theme.yaml name: Marketing components: none
Templates
Accent CMS uses MiniJinja, a Jinja2-compatible template engine.
Base Template
Create templates/base.html.jinja:
<!DOCTYPE html>
<html lang="{{ site.language }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{{ page.title }} - {{ site.name }}{% endblock %}</title>
{% for css in theme.assets.css %}
<link rel="stylesheet" href="/theme/assets/{{ css }}">
{% endfor %}
</head>
<body>
<header>
<nav>
<a href="/" class="site-title">{{ site.name }}</a>
<ul>
{% for p in pages %}
{% if p.menu.visible %}
<li><a href="{{ p.url }}">{{ p.title }}</a></li>
{% endif %}
{% endfor %}
</ul>
</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© {{ site.name }}</p>
</footer>
{% for js in theme.assets.js %}
<script nonce="{{ nonce }}" src="/theme/assets/{{ js }}"></script>
{% endfor %}
</body>
</html>
Accent copies the files theme.yaml declares under assets: to the site, but adds none of them to a page: the two loops above are what load them. A script the theme declares and no template loads never runs.
The nonce="{{ nonce }}" attribute lets the script run under the strict script policy. The next section explains it.
Scripts and the content security policy
accent serve sends every page a Content-Security-Policy with script-src 'self' 'nonce-...', where the nonce is a random value that changes on every response. A browser then runs a script only when it meets one of these conditions:
- It is an external script from the site’s own origin, such as
<script src="/theme/assets/app.js">. - It carries the response’s nonce, which your template writes with
nonce="{{ nonce }}".
Anything else is blocked, including an inline <script> without the nonce, an inline event handler such as onclick="..." or onload="...", and a javascript: URL. A script that reaches a page through content or an escaping bug therefore does not run.
To make a theme work under the policy:
-
Add
nonce="{{ nonce }}"to every<script>element in your templates, inline or external. A script from a CDN that carries the nonce needs no allowlist entry.<script nonce="{{ nonce }}"> document.documentElement.dataset.theme = localStorage.getItem('theme') || 'light'; </script> <script nonce="{{ nonce }}" defer src="https://cdn.jsdelivr.net/npm/katex@0.19.0/dist/katex.min.js"></script> -
Move inline event handlers into a script. A nonce cannot allow an
onclickoronchangeattribute.<select class="version-select">...</select> <script nonce="{{ nonce }}"> document.currentScript.previousElementSibling.addEventListener('change', function () { window.location.href = this.value; }); </script> -
Check the browser console for
Content-Security-Policyerrors. To find them without blocking anything, sethttp_headers.security.script_policy.report_only: true.
{{ nonce }} is available in every template, including partials and shortcode components. A script that a script creates, such as a <script> element the page adds at run time or a library loaded with import(), is allowed when it comes from the site’s own origin or from a nonced module. A library that loads more scripts from another origin, such as the Google tag, needs that origin in script_policy.sources.
accent build writes no nonce, because a built page has no response to stamp one into. It removes the nonce="{{ nonce }}" attribute instead and allows each marked script by source: a 'sha256-...' hash of every marked inline script, and the origin of every marked script from another host. The header artifacts (build.header_artifacts) and accent serve-static send those sources in script-src. The attribute is what tells the build a script belongs to your theme, so a script without it is blocked in static output too. A protocol-relative script, such as src="//ajax.googleapis.com/...", is allowed by its host with no scheme (ajax.googleapis.com), which Content Security Policy matches against the scheme the page is served over, so the script loads whether your site is served over HTTPS or HTTP. As for every source allowed by its host, that stops holding if you add 'strict-dynamic' to script_policy.sources: a browser then ignores host sources.
Each distinct inline script adds one hash to the policy. A script that interpolates a per-page value, such as var id = "{{ page.url }}", produces a different hash on every page. Keep per-page values in data attributes and read them from a script whose text does not change, or move the script into a file under assets/.
For the configuration, including how to turn the policy off, see the configuration reference.
Page Template
Create templates/default.html.jinja:
{% extends "base.html.jinja" %}
{% block content %}
<article>
<h1>{{ page.title }}</h1>
{% if page.date %}
<time>{{ page.date }}</time>
{% endif %}
<div class="content">
{{ page.content | safe }}
</div>
{% if page.tags %}
<div class="tags">
{% for tag in page.tags %}
<span class="tag">{{ tag }}</span>
{% endfor %}
</div>
{% endif %}
</article>
{% endblock %}
Available Template Variables
The tables in this section are generated from the context types the
renderer serialises, so they list every variable and field a template can
read, with its type. accent docs template-context prints the same
reference, and MCP clients read it as the accent://templates/context
resource. The subsections after the tables cover the variables that need
more than a row.
Every template receives the variables below. The tables are generated from the context types, so each one lists every field a template can read. Types are written as a template experiences them: String? is a value that can be none, Vec<T> is a list, Map<String, T> is a mapping iterated in key order, Value is any JSON-shaped value, and URL is a string already joined with site.base_path. Nested types are listed under Types. Request and build lists the values the engine injects itself.
site
Site configuration and metadata.
Type: SiteContext
| Variable | Type | Description |
|---|---|---|
site.base_path | String | Deployment path prefix for sub-path hosting. |
site.built_at | String? | Timestamp when the site was built or the server started. |
site.current_language | String | The language of the currently rendered page. |
site.description | String | Site description. |
site.extra | Map<String, Value> | Free-form site-wide values from site.extra in config.yaml, exposed read-only. An empty mapping when the config has no extra key, so {% if site.extra.x %} is always safe. Reloaded with the config. |
site.language | String | Site language. |
site.languages | Vec<String> | All configured language codes. |
site.last_modified | String? | Most recent page modification date across all content. |
site.main_site | MainSiteContext? | Optional main-site link block from site.main_site in config. |
site.name | String | Site name. |
site.production | bool | Whether the site is being served or built in production mode. |
site.url | String | Site URL. |
page
Current page data.
Type: PageContext
| Variable | Type | Description |
|---|---|---|
page.author | String? | Author name from frontmatter. |
page.backlinks | Vec<Backlink> | Pages that link to this page via wikilinks. |
page.body_schema | BodySchemaContext? | Body and taxonomy schema metadata for the resolved document model. |
page.breadcrumbs | Vec<PageMeta> | Breadcrumb trail from root to parent. |
page.canonical_url | URL | The URL this page is advertised under. |
page.children | Vec<PageMeta> | Direct children pages, sorted by menu.order. |
page.collection | Vec<PageMeta> | Collection items for listing pages. |
page.content | String | Rendered HTML content (markdown converted to HTML). |
page.custom | Map<String, Value> | Custom frontmatter fields (arbitrary key-value pairs), in key order. |
page.date | String? | Publication date from frontmatter. |
page.description | String | Page-specific meta description. |
page.direction | String | Writing direction for the page’s content: "ltr" or "rtl". |
page.has_math | bool | Whether the page contains math expressions. |
page.islands | Vec<IslandRef> | Island components referenced in this page’s markdown. Used by templates for conditional script loading. |
page.json_ld | String | Pre-computed JSON-LD structured data HTML. |
page.language | String | Language code for this page. |
page.lead | String? | Short summary for listings, feeds, and the meta description. |
page.locale | String | Typographic locale used for smart-quote remapping. |
page.markdown_url | URL? | URL to view this page’s raw markdown source. |
page.media | Vec<MediaAssetContext> | Media files in the same directory as this page. |
page.meta_tags | Vec<MetaTag> | Merged, ordered social/SEO <meta> tags (site defaults + page meta:). |
page.model | String? | Resolved document model name. |
page.modified_date | String? | File modification date as YYYY-MM-DD string. |
page.modules | Vec<ModuleContext> | Page modules from _-prefixed subdirectories. |
page.nav_title | String | Short navigation label for the current page, preferred over title in navigation surfaces (the page’s own breadcrumb crumb, active top-nav item). Resolves to menu.title when set, otherwise title. |
page.next | PageMeta? | Next page in the sibling sequence. |
page.noindex | bool | Whether the page asked to be kept out of indexes. |
page.order | int | Menu sort order from menu.order frontmatter. |
page.pagination | Pagination? | Pagination metadata for collection pages. |
page.parent | PageMeta? | Parent page metadata. |
page.prev | PageMeta? | Previous page in the sibling sequence. |
page.reading_time | int | Estimated reading time in minutes. |
page.redirect | URL? | Redirect target URL, flattened from RedirectConfig. |
page.relations | Map<String, Vec<PageMeta>> | Named relationships to other pages. |
page.rewrite | URL? | Rewrite target URL. |
page.sections | Map<String, String> | Named content sections, each rendered to HTML. |
page.siblings | Vec<PageMeta> | Sibling pages (same parent), excluding self. |
page.social_image | URL? | Absolute URL of the page’s social-card image, if any. |
page.status | String | Publishing status as a string. |
page.tags | Vec<String> | Tags from frontmatter. |
page.taxonomies | Map<String, Vec<String>> | The configured taxonomies’ terms this page carries, keyed by taxonomy name and normalized to a list whatever the frontmatter spelling: categories: praxis and categories: [praxis] both give page.taxonomies.categories == ["praxis"], the list a listing entry carries as p.taxonomies.categories. A taxonomy the page does not use has no key, so read one as page.taxonomies.categories | default([]). Empty on a site without a taxonomies: key. |
page.title | String | Page title from frontmatter. |
page.toc | Vec<TocEntry> | Table of contents entries extracted from headings: the body’s, then each section’s, in the order of the section names. |
page.translations | Vec<TranslationLink> | Available translations of this page. |
page.url | URL | Page URL path (e.g., /docs/getting-started), joined with the deployment base path. |
page.word_count | int | Word count of the raw markdown content. |
theme
Theme configuration.
Type: ThemeContext
| Variable | Type | Description |
|---|---|---|
theme.assets | AssetsContext | Theme assets. |
theme.heading_anchor_style | String | Heading anchor presentation style: one of "copy", "gutter", "hash", "pilcrow". |
theme.name | String | Theme name. |
theme.styling | StylingContext | Styling configuration exposed to templates. |
dev
Development settings.
Type: DevContext
| Variable | Type | Description |
|---|---|---|
dev.browser_reload | bool | Whether browser auto-reload is enabled. |
dev.debug | bool | Whether debug mode is enabled. |
dev.hot_reload | bool | Whether theme hot reload is enabled. |
taxonomy
Taxonomy data for tag navigation.
Type: TaxonomyContext
| Variable | Type | Description |
|---|---|---|
taxonomy.current_label | String? | The current term’s label, its authored spelling, on a term page of the tags or of a configured taxonomy: print this one, as the page title does. |
taxonomy.current_tag | String? | The current tag being viewed (on /tags/{tag} pages), as its lowercase name: compare it with tag.name. |
taxonomy.current_term | String? | The current term’s lowercase name on a term page of the tags or of a configured taxonomy: compare it with term.name, as current_tag is compared on the tag pages. |
taxonomy.name | String? | On a taxonomy’s own pages, the taxonomy they render: tags, or a configured taxonomy’s name. Pass it to term_url(). None on every other page. |
taxonomy.pages | Vec<PageMeta> | Pages matching the current tag (on /tags/{tag} pages), URLs joined. On a configured taxonomy’s term page, the term’s pages. |
taxonomy.singular | String? | On a taxonomy’s own pages, the taxonomy’s singular: tag, or a configured taxonomy’s singular. None on every other page. |
taxonomy.tags | Vec<TagInfo> | The tags of the pages the tag pages list, with their counts, sorted alphabetically: on a site with several languages, those of the page’s language. |
taxonomy.terms | Vec<TagInfo> | On a taxonomy’s own pages, that taxonomy’s terms in the page’s language: the entries of taxonomy.tags on the tag pages and of taxonomy.<name> on a configured taxonomy’s. Empty on every other page. |
version
Version information for versioned content.
Type: VersionInfo?
| Variable | Type | Description |
|---|---|---|
version.all | Vec<VersionOption> | Every version of the root, for switchers. |
version.badge | String? | Badge of the current version (latest, previous, …), when set. |
version.current | String | The version the page is rendered for. |
version.is_fallback | bool | Whether the page was served from another version as a fallback. |
version.label | String | Display label of the current version. |
version.root | URL | The versioning root URL, joined with the base path. |
version.source | String | The version the content actually came from. |
version.state | String | Release state of the current version (released, dev, …). |
error
Error context for custom error pages.
Type: ErrorContext?
| Variable | Type | Description |
|---|---|---|
error.language | String | Detected language code for this request. |
error.message | String | Sanitized error message. In dev mode, includes full details. In production, contains only safe-to-display text. |
error.path | String | The request path that triggered the error. |
error.status | int | HTTP status code (e.g., 404, 500). |
error.title | String | Human-readable error title (e.g., “Page Not Found”). |
request
HTTP request context (path and query parameters).
Type: RequestContext?
| Variable | Type | Description |
|---|---|---|
request.path | String | URL path (e.g., “/contact”). Not joined: it is the path requested, which already carries any base path. |
request.query | Map<String, String> | Query parameters as key-value pairs, in key order. Duplicate keys: last value wins. |
diagrams
Diagram renderer settings surfaced to templates (diagrams.mermaid.cdn_url and friends).
Type: DiagramsContext
| Variable | Type | Description |
|---|---|---|
diagrams.mermaid | DiagramsMermaidContext | Mermaid client-side loader configuration. |
accent
Build and version information about the running Accent binary (accent.version, accent.version_string, …).
Type: AccentContext
| Variable | Type | Description |
|---|---|---|
accent.build_time | String | UTC build timestamp. |
accent.git_hash | String | Short git commit hash the binary was built from. |
accent.version | String | Semantic version (e.g. 0.26.0-dev). |
accent.version_string | String | Combined version and hash (e.g. 0.26.0-dev (abc1234)). |
Other variables
Root values that are not structs, read directly by name.
| Variable | Type | Description |
|---|---|---|
pages | Vec<PageMeta> | Menu-visible pages of the rendered page’s language (for navigation), URLs joined. |
all_pages | Vec<PageMeta> | All pages including hidden ones (for listings), URLs joined. |
markdown_view_enabled | bool | Whether Markdown View is enabled. |
diagram_passthrough_count | int | Number of <pre class="diagram-passthrough"> blocks the dispatcher emitted while rendering this page’s markdown. |
_json_ld | String | The page’s JSON-LD block, at the top level for the json_ld() template function. The same string as page.json_ld. |
Request and build
Values the engine injects outside the context types. Every template can read them.
| Variable | Type | Description |
|---|---|---|
nonce | String | Write nonce="{{ nonce }}" on every <script> element. Under accent serve it is the per-response nonce the Content-Security-Policy names: an inline script, or one from another host, runs only with it; one from the site’s own origin runs either way. Under accent build it marks the script as the theme’s: the build allows an inline script by its hash and an external one by its origin, removes the attribute, and blocks a script without it. |
Types
Shapes referenced by the tables above. A field of type PageMeta? is either none or a mapping with the fields listed here.
MainSiteContext
Main-site link data exposed as site.main_site.
| Field | Type | Description |
|---|---|---|
badge_label | String? | Back-to-home chip text; the default theme falls back to "Docs" when None. |
cta_label | String? | Call-to-action button text. |
cta_url | String? | Call-to-action target URL. |
host | String | Display host derived from url: scheme, userinfo, port, path, query, and fragment stripped (e.g. https://a:b@x.dev:8443/p?q -> x.dev). Falls back to the raw URL (sans trailing /) when no host can be extracted. Themes use it for compact link text. |
label | String? | Wordmark text; templates fall back to site.name when None. An explicit empty string is honored (icon-only wordmark). |
url | String | Main-site URL the brand wordmark links to. |
Backlink
A page that links to the current page through a wikilink.
| Field | Type | Description |
|---|---|---|
title | String | Title of the linking page. |
url | URL | URL of the linking page, joined with the base path. |
BodySchemaContext
Read-only schema view exposed to templates.
| Field | Type | Description |
|---|---|---|
body | BodySchema? | Body structure constraints, or None when the model has no body: section. |
taxonomy | TaxonomySchema? | Taxonomy field constraints, or None when the model has no taxonomy: section. |
PageMeta
A page as it appears in a listing: navigation, children, breadcrumbs, collections, relations, taxonomy pages.
| Field | Type | Description |
|---|---|---|
author | String? | Author, when set. |
custom | Map<String, Value> | Custom (non-built-in) frontmatter keys, the same passthrough that fills page.custom. Lets a listing select or show pages by author-defined keys: {% for p in all_pages if p.custom.category == "praxis" %}. |
date | String? | Publication date (YYYY-MM-DD), when set. |
hierarchy_url | String | The directory-derived URL the hierarchy is keyed on: root-relative (no base path) and a key, not a link. The same as url less the base path unless a permalink pattern or a frontmatter url: moved the page’s public URL; page.parent, page.children, page.breadcrumbs, and nav_tree() all nest on it. |
lead | String? | Short summary for listings. |
model | String? | Resolved document model name, when the page has one. |
modified_date | String? | File modification date (YYYY-MM-DD). |
nav_title | String | Short navigation label, menu.title when set, otherwise title. |
order | int | Menu sort order. |
tags | Vec<String> | Tags. |
taxonomies | Map<String, Vec<String>> | The configured taxonomies’ terms this page carries, keyed by taxonomy name and normalized to a list whatever the frontmatter spelling, so {% for p in all_pages if 'praxis' in p.taxonomies.categories %} reads as the author thinks. Absent from serialized output when empty, which it is for a site without a taxonomies: key. |
title | String | Page title. |
url | URL | Page URL, joined with the deployment base path. |
IslandRef
Reference to an island component used in a page.
| Field | Type | Description |
|---|---|---|
component | String | Component name (maps to JS module in theme or plugin). |
js_path | String | JS asset path relative to plugin dir (empty for theme islands). |
plugin | String | Plugin name (empty for theme islands). |
source | String | Source of the island: "theme" or "plugin". |
MediaAssetContext
Template-facing media asset context.
| Field | Type | Description |
|---|---|---|
filename | String | Filename without directory (e.g., hero.jpg). |
height | int? | Image height in pixels (images only). |
media_type | String | Type label: "image", "video", "audio", "document", "archive", "other". |
metadata | Map<String, Value> | Plugin-contributed metadata. |
mime_type | String | MIME content type (e.g., image/jpeg). |
size_bytes | int | File size in bytes. |
url | URL | Serving URL (e.g., /content-media/blog/post/hero.jpg), joined with the deployment base path. |
width | int? | Image width in pixels (images only). |
MetaTag
A single resolved social/SEO <meta> tag for the head-meta partial.
| Field | Type | Description |
|---|---|---|
key | String | The tag key (e.g. og:type, twitter:site, fediverse:creator). |
property | bool | true to emit property=, false to emit name=. |
value | String | The tag value (the content="..." attribute). |
ModuleContext
Rendered module available in template context.
| Field | Type | Description |
|---|---|---|
content | String | Rendered HTML content. |
custom | Map<String, Value> | Custom frontmatter fields, in key order. |
name | String | Module name (e.g., "hero" from _hero directory). |
title | String | Module title from frontmatter. |
Pagination
Pagination state of a collection page.
| Field | Type | Description |
|---|---|---|
current_page | int | The current page number, starting at 1. |
items_per_page | int | Items per page. |
next_url | URL? | URL of the next page, joined with the base path; None on the last page. |
prev_url | URL? | URL of the previous page, joined with the base path; None on the first page. |
total_items | int | Total number of items across all pages. |
total_pages | int | Total number of pages. |
TocEntry
A single entry in the table of contents.
| Field | Type | Description |
|---|---|---|
children | Vec<TocEntry> | Child entries (sub-headings). |
id | String | Generated anchor ID (e.g., “getting-started”). |
level | int | Heading level (1-6). |
text | String | Heading text content. |
TranslationLink
A translation of the current page, for language switchers.
| Field | Type | Description |
|---|---|---|
active | bool | Whether this entry is the page being rendered. |
language | String | Language code of the translation. |
url | URL | URL of the translated page, joined with the base path. |
AssetsContext
Theme assets context.
| Field | Type | Description |
|---|---|---|
css | Vec<String> | CSS files. |
js | Vec<String> | JavaScript files. |
StylingContext
Styling context exposed to templates.
| Field | Type | Description |
|---|---|---|
tailwind_cdn | bool | Whether the Tailwind v4 CDN is enabled. |
tailwind_config | String? | Optional Tailwind CDN custom theme config. |
TagInfo
Tag metadata for template rendering.
| Field | Type | Description |
|---|---|---|
count | int | Number of pages tagged with this tag. |
label | String | The tag as authors write it: name with its first spelling’s case, so Beratung aare for the name beratung aare. Print this one. |
name | String | Normalized tag name (lowercase). A page’s tags keep the author’s spelling, and spellings that share a slug are one tag, so to ask whether a page carries this tag compare URLs, which come from the slug: tag_url(t) == tag_url(tag) for a page tag t. |
slug | String | The tag’s URL segment: /tags/{slug} is its page. |
VersionOption
One version in a version switcher.
| Field | Type | Description |
|---|---|---|
badge | String? | Badge, when set. |
id | String | Version identifier. |
is_current | bool | Whether this is the version being rendered. |
label | String | Display label. |
state | String | Release state. |
url | URL | URL of this page in that version, joined with the base path. |
DiagramsMermaidContext
Mermaid loader values surfaced to templates.
| Field | Type | Description |
|---|---|---|
cdn_url | String | Mermaid.js URL imported by the default-theme loader. |
sri_hash | String | Subresource Integrity hash for cdn_url. Surfaced to templates for forward-compatibility; the default-theme loader does not emit integrity= yet (ES import cannot carry SRI directly – the planned path is <link rel="modulepreload" integrity="...">). |
theme | String | Theme name passed to mermaid.initialize({ theme }). |
BodySchema
Body structure schema for a document model.
| Field | Type | Description |
|---|---|---|
elements | Map<String, ElementConstraint> | Unordered element count constraints, keyed by element type. |
forbid | Vec<BodyElementType> | Element types forbidden at the top level of the body. |
sequence | Vec<SequenceStep> | Ordered sequence pattern. Parsed at model-load time; validation against an actual body structure is performed by a later iteration. |
TaxonomySchema
Taxonomy constraints for a document model.
| Field | Type | Description |
|---|---|---|
fields | Map<String, TaxonomyFieldConstraint> | Per-taxonomy-field constraints, keyed by field name (e.g., "tags"). |
ElementConstraint
Count constraints for a body element type.
| Field | Type | Description |
|---|---|---|
max | int? | Most occurrences allowed; None leaves the upper bound open. |
min | int? | Fewest occurrences allowed; None leaves the lower bound open. |
BodyElementType
Top-level markdown element type for body structure analysis.
| Value | Description |
|---|---|
h1 | |
h2 | |
h3 | |
h4 | |
h5 | |
h6 | |
paragraph | |
ordered_list | |
unordered_list | |
blockquote | |
table | |
footnote_definition | |
list | Matches OrderedList OR UnorderedList during constraint application. |
code | Fenced or indented code block. |
image | Top-level image (a paragraph containing exactly one image and no other inline content). |
hr | Thematic break. |
html | Top-level raw HTML block. |
definition_list | Definition list block (term\n: definition). |
math_block | Display math block ($$ ... $$); inline math is never recorded. |
admonition | GitHub-style admonition blockquote (> [!NOTE], > [!WARNING]). Plain blockquotes (no kind marker) continue to map to Self::Blockquote; the Self::Blockquote constraint matches both via the union semantic in Self::matches. |
metadata_block | YAML / TOML metadata block embedded in the body (---\nkey: value\n--- outside the page frontmatter). |
SequenceStep
A single step in an ordered body sequence pattern. Parsed at model-load time; validation against an actual body structure is performed by a later iteration.
| Field | Type | Description |
|---|---|---|
element | Vec<BodyElementType> | Element types that satisfy this step (OR semantics). |
max | int? | Maximum elements to consume (None = unbounded). |
min | int | Effective minimum elements to consume. When the raw YAML sets min: N it is taken verbatim; otherwise the minimum is 1 for required: true (the default) and 0 for required: false. |
TaxonomyFieldConstraint
Constraints on a single taxonomy array field.
| Field | Type | Description |
|---|---|---|
allowed | Vec<String> | If non-empty, only these values are allowed (case-insensitive). |
count | int? | Exact number of values required; None when only the bounds apply. |
max | int? | Most values allowed; None leaves the upper bound open. |
min | int? | Fewest values allowed; None leaves the lower bound open. |
required_one_of | Vec<String> | At least one value from this list must be present. |
Linking Back to a Main Site
A docs (or other satellite) site often fronts a separate product or
marketing site. Declaring that relationship in config.yaml lets the
theme render navigation back to it:
site: main_site: url: https://example.com label: Example # optional; defaults to site.name badge_label: Docs # optional; text of the back-to-home chip cta_label: Get a license # optional; CTA renders only with both cta_* keys cta_url: https://example.com/pricing
The bundled default theme reacts to the block in three places: the
header wordmark links to main_site.url while a small chip
(badge_label, default “Docs”) next to it keeps the way back to the
site’s own home page, an accent-colored button in the header links
cta_label to cta_url, and the footer carries main_site.host –
the host name derived from the URL. Without the block, none of this
renders and the wordmark links to the site’s own home page. In your
own templates, gate on the block the same way:
{% if site.main_site %}
<a href="{{ site.main_site.url }}">{{ site.main_site.host }}</a>
{% endif %}
Page URL and Canonical URL
page.url and page.canonical_url differ for one page. A home page in a home/ directory is reachable
at both /home and the site root, because accent serve resolves the root
through it and accent build writes the root index.html from it. Both
return 200 with the same content, so the page needs a single URL to be
advertised under, and that is the root. Point your canonical tag at
page.canonical_url and the tag will agree with what sitemap.xml lists;
point it at page.url and the two will name different URLs for one page,
which is worse than either on its own – a search engine then has two
contradictory claims and picks between them itself.
Tag Routes
Tag index and per-tag listing pages are served at /tags and /tags/{tag}. Create tags.html.jinja and tag.html.jinja templates to customize their appearance. See the Templating Guide for full examples.
Built-in Styling Pipeline
Accent CMS includes a built-in CSS processing pipeline that handles Sass/SCSS compilation, Tailwind-style utility CSS, and production minification – no Node.js required. Configure it in your theme.yaml:
styling: sass: enabled: true utilities: enabled: true scan: - templates/**/*.jinja processing: minify: true
See the Styling Guide for full documentation.
Styling Tips
- Mobile-first - Start with mobile styles, then add breakpoints
- CSS Variables - Use custom properties for easy theming
- Semantic HTML - Use proper elements for accessibility
- Minimize dependencies - Keep your CSS lean and fast
Activating Your Theme
Set your theme in config.yaml:
theme: name: my-theme
Then restart the server to see your changes.