AI-Assisted Design
If you use an AI design tool like Claude to create or modify an aphid theme, you can get better results by giving it the full picture of what a theme requires. This page provides a ready-made instruction block covering the theme format, required templates, and every variable available in the Tera template context.
For background on project-level instruction files and where each tool expects them, see AI-Assisted Writing.
Tip
Run aphid agent claude (or copilot, codex) to write these files for you in the right location with the right frontmatter. See CLI reference > aphid-agent for the full description.
Instructions
Copy the block below into your tool’s project-level instruction file.
This project uses aphid, a static site generator. Themes are directories containing Tera
templates (Jinja2-style) and optional static files. The goal is to design a complete theme.
## Theme directory layout
```
mytheme/
theme.toml
templates/
base.html
home.html
blog_post.html
blog_index.html
wiki_page.html
wiki_index.html
page.html
tag.html
tags_index.html
pagination.html
404.html
static/
css/
js/
```
`theme.toml` is required and must contain at least:
```toml
name = "mytheme"
version = "0.1.0"
```
`description` is optional.
## Template engine
Templates use Tera — a Jinja2-style engine. Key syntax:
- `{{ variable }}` — output a value
- `{{ variable | safe }}` — output HTML without escaping (required for rendered content)
- `{% block name %}...{% endblock %}` — define/override blocks
- `{% extends "base.html" %}` — inherit from a parent template
- `{% for item in list %}...{% endfor %}` — loops
- `{% if condition %}...{% elif %}...{% else %}...{% endif %}` — conditionals
- `{# comment #}` — comments
The standard pattern is a `base.html` layout that all other templates extend.
## Global variables (available in every template)
| Variable | Type | Description |
|----------|------|-------------|
| `site_title` | string | Site title from `aphid.toml` |
| `site_description` | string? | Site description from `aphid.toml` |
| `social_image_url` | string? | Default OpenGraph image URL |
| `version` | string | The aphid binary version |
| `nav_pages` | list | Standalone pages sorted by `order`; each has `title` and `url` |
| `socials` | list | Social links from `aphid.toml`; each has `platform` and `url` |
| `favicon_tags` | string | Pre-rendered `<link rel="icon">` tags |
| `feed_atom_url` | string | URL for the Atom feed |
| `feed_rss_url` | string | URL for the RSS feed |
## base.html
The root layout. All other templates extend this. Must define blocks that child templates
override. Typically contains `<html>`, `<head>`, navigation, header, footer.
Example skeleton:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block page_title %}{{ site_title }}{% endblock %}</title>
<link rel="stylesheet" href="/static/css/theme.css">
{{ favicon_tags | safe }}
</head>
<body>
<nav>
<a href="/">{{ site_title }}</a>
{% for page in nav_pages %}
<a href="{{ page.url }}">{{ page.title }}</a>
{% endfor %}
</nav>
<main>
{% block content %}{% endblock %}
</main>
<footer>
{% for social in socials %}
<a href="{{ social.url }}">{{ social.platform }}</a>
{% endfor %}
<span>Built with aphid {{ version }}</span>
</footer>
</body>
</html>
```
## home.html
Renders the site root (`/index.html`).
| Variable | Type | Description |
|----------|------|-------------|
| `posts` | list | All blog posts (see post entry shape below) |
| `home` | object? | Present when `content/home.md` exists; has `content` (rendered HTML — use `\| safe`) |
| `contains_mermaid` | bool | True if any rendered block uses Mermaid |
| `popular_tags` | list | All tags across blog and wiki, sorted by descending count then ascending name. Each entry has `name`, `slug`, `count`. Counts match `/tags/`. Slice with `\| slice(end=N)` for top-N. |
## blog_post.html
Renders a single blog post.
| Variable | Type | Description |
|----------|------|-------------|
| `title` | string | Post title |
| `url` | string | Clean URL, e.g. `/blog/my-post/` |
| `canonical_url` | string | Absolute canonical URL |
| `content` | string | Rendered HTML body — always use `\| safe` |
| `toc` | list | Heading entries; each has `level` (int), `text` (string), `id` (string) |
| `author` | object | `{ name, link?, image? }` |
| `image` | string? | Hero image path or URL |
| `og_image` | string? | Absolute social-share image URL |
| `description` | string? | Short summary |
| `created` | string | Publication date `YYYY-MM-DD` |
| `updated` | string? | Last-edited date |
| `reading_time_minutes` | int | Estimated read time |
| `tags` | list | Each has `name` and `slug` |
| `newer_post` | object? | `{ title, url }` for navigation |
| `older_post` | object? | `{ title, url }` for navigation |
| `contains_mermaid` | bool | |
## blog_index.html
Renders the blog listing at `/blog/`.
| Variable | Type | Description |
|----------|------|-------------|
| `posts` | list | Posts on this page (see post entry shape below) |
| `pagination` | object? | Pagination state |
## Post entry shape (used in home.html, blog_index.html, tag.html)
| Field | Type | Description |
|-------|------|-------------|
| `title` | string | Post title |
| `url` | string | Clean URL |
| `created` | string? | Publication date |
| `image` | string? | Hero image path or URL |
| `description` | string? | Short summary from frontmatter |
| `reading_time_minutes` | int | Reading-time estimate (rounded up, minimum 1) — same value as on the post template |
| `tags` | list | Each has `name` and `slug` |
## wiki_page.html
Renders a single wiki page. Has many of the same variables as `blog_post.html`, plus:
| Variable | Type | Description |
|----------|------|-------------|
| `category` | string | Category name. Falls back to `wiki_default_category` (default `"Other"`) when frontmatter omits it, so always non-empty |
| `backlinks` | list | Pages linking here; each has `title` and `url` |
| `wiki_categories` | list | All wiki pages grouped by category — for a sidebar. Each entry has `name` (string), `description` (string, optional), `icon` (string, optional — full `/static/…` path to an SVG), and `pages` (list of `{title, url}`). Named categories listed in `wiki_categories` config appear first, then alphabetical; the default catch-all group sorts last. |
`author`, `image` are always absent on wiki pages. `created`, `updated`, `tags` are present
only if set in frontmatter.
## wiki_index.html
Renders the wiki listing at `/wiki/`.
| Variable | Type | Description |
|----------|------|-------------|
| `categories` | list | Same shape as `wiki_categories` on `wiki_page.html` |
| `wiki_intro` | object? | Present when `content/wiki.md` exists. Has `content` (string — rendered HTML, pass through `\| safe`). |
| `contains_mermaid` | bool | `true` when `wiki.md` contains mermaid blocks. |
## page.html
Renders standalone pages (About, Contact, etc.). Same variables as `blog_post.html`, but
`author`, `image`, `created`, `updated`, and `tags` are always absent.
## tag.html
Renders a single tag page.
| Variable | Type | Description |
|----------|------|-------------|
| `tag` | string | Tag display name |
| `tag_slug` | string | URL-safe slug |
| `blog_posts` | list | All blog posts with this tag (post entry shape). Guard with a length check — may be empty |
| `wiki_pages` | list | All wiki pages with this tag. Same shape. Also may be empty |
Tag pages are not paginated.
## tags_index.html
Renders the tag listing at `/tags/`.
| Variable | Type | Description |
|----------|------|-------------|
| `tags` | list | All tags; each has `name`, `slug`, and `count` |
## pagination.html
Included by listing templates (`blog_index.html`). Receives the surrounding template's
`pagination` object:
| Field | Type | Description |
|-------|------|-------------|
| `current` | int | 1-indexed current page number |
| `total` | int | Total number of pages |
| `prev_url` | string? | URL of the previous page, `null` on page 1 |
| `next_url` | string? | URL of the next page, `null` on the last page |
| `pages` | list | Every page — each entry has `n` (int) and `url` (string) for numeric nav |
`pagination` is `null` when the entire listing fits on one page; templates should guard
the include with `{% if pagination %}`. Tag pages are not paginated — `tag.html` never
receives a `pagination` variable.
## 404.html
Error page.
| Variable | Type | Description |
|----------|------|-------------|
| `not_found` | object? | Present when `content/404.md` exists; has `content` (use `\| safe`) |
| `contains_mermaid` | bool | |
## Static files and CSS
Place stylesheets, scripts, and other assets in `mytheme/static/`. They are copied to the
output's `static/` directory. Reference them with absolute paths:
```html
<link rel="stylesheet" href="/static/css/theme.css">
```
If the user's `static_dir` has a file with the same name, the user's version wins.
## Syntax highlighting CSS
Code blocks use CSS classes prefixed `hl-`. The theme stylesheet must define colors for
these classes. Key token classes:
- `hl-keyword` — language keywords (`fn`, `if`, `return`)
- `hl-string` — string literals
- `hl-comment` — comments
- `hl-type` — type names
- `hl-function` — function/method names
- `hl-number` — numeric literals
- `hl-operator` — operators
- `hl-punctuation` — brackets, commas, semicolons
- `hl-variable` — variable names
- `hl-attribute` — attributes/decorators
- `hl-tag` — HTML/XML tags
- `hl-entity` — entities and special names
Wrap code blocks in a container with `overflow-x: auto` for horizontal scrolling. Use a
monospace font and a background color that contrasts with the page.
## Mermaid diagrams
When `contains_mermaid` is true, the template should load `/static/js/mermaid.min.js`
(bundled by aphid) and initialise it. The renderer wraps Mermaid blocks in
`<pre class="mermaid">`, which Mermaid picks up on initialise. Gate the script tag on
`contains_mermaid` so pages without diagrams don't pay the download cost:
```html
{% if contains_mermaid %}
<script src="/static/js/mermaid.min.js"></script>
<script>mermaid.initialize({ startOnLoad: true });</script>
{% endif %}
```
## Social meta tags
`base.html` is expected to emit OpenGraph and Twitter card meta tags in the `<head>` of
every page. Two Tera blocks are reserved for per-template overrides:
| Block | Default | Override on |
|-------|---------|-------------|
| `og_type` | `"website"` | `blog_post.html` → `"article"` |
| `article_meta` | empty | `blog_post.html` → `article:published_time`, `article:modified_time`, `article:author`, `article:tag` |
Tag content comes from these context fields, with fallbacks:
- `og:title` / `twitter:title` — page `title`, falling back to `site_title`
- `og:description` / `twitter:description` / `<meta name="description">` — page `description`, falling back to `site_description`
- `og:url` — page `canonical_url` (only emitted when the page exposes one)
- `og:image` / `twitter:image` — blog post `og_image`, falling back to `social_image_url`
- `twitter:card` — `summary_large_image` when an image is set, `summary` otherwise
- `og:site_name` — `site_title`
Pages without an image still produce valid tags — they just drop the `og:image` /
`twitter:image` lines and downgrade the card type to `summary`.
## Design guidelines
- The page title is `<h1>`; body headings start at `<h2>` (the markdown pipeline shifts
heading levels up by one).
- `content` is rendered HTML — use `{{ content | safe }}`.
- `toc` entries can build a table of contents sidebar or in-page nav.
- `backlinks` are most useful on wiki pages — show them in a footer or sidebar section.
- `wiki_categories` on `wiki_page.html` enables a sidebar showing all wiki pages grouped by
category, with the current page highlighted (compare `page.url == url`). Each category
may also have `description` and `icon` for richer rendering.
- `wiki_intro` on `wiki_index.html` renders an optional intro section above the category cards.
- Test the theme against pages with: no image, no tags, no TOC, very long content, many
backlinks, and the 404 page.
- Ensure the layout is responsive — test at mobile, tablet, and desktop widths.
See also: Themes, Markdown, AI-Assisted Writing.