Markdown

aphid uses pulldown-cmark to render Markdown, with a small set of extensions enabled on top of standard CommonMark.

Standard elements

These work out of the box:

ElementSyntax
ParagraphPlain text separated by blank lines
Bold**text** or __text__
Italic*text* or _text_
Inline code`code`
Link[text](url)
Image![alt](url)
Blockquote> text
Unordered list- item or * item
Ordered list1. item
Horizontal rule---
Hard line breakTwo trailing spaces

Headings

Headings are written with # markers. Because the page title is rendered as <h1> by the template, the markdown pipeline shifts all heading levels up by one — so # becomes <h2>, ## becomes <h3>, and so on. Levels are capped at <h6>.

Every heading automatically receives a slug-based id attribute for anchor links, and is included in the table of contents passed to the template.

# Top-level section    → <h2 id="top-level-section">
## Subsection          → <h3 id="subsection">

When two headings on the same page produce the same slug (e.g. two # Examples sections), the second gets -2, the third -3, and so on, so anchor links remain unique.

Code blocks

Fenced code blocks are syntax-highlighted using syntect. The highlighter emits CSS classes (prefixed hl-) rather than inline styles, so syntax colors are fully controlled by the theme stylesheet. The default theme uses Catppuccin Mocha colors. Specify a language identifier after the opening fence:

```rust
fn main() {
    println!("Hello, world!");
}
```

Here is a rendered example:

use std::collections::HashMap;

/// A page in the site, parameterised by its frontmatter type.
pub struct Page<F> {
    pub slug: String,
    pub body: String,
    pub frontmatter: F,
}

fn process(pages: &[Page<BlogFrontmatter>]) -> HashMap<String, String> {
    let mut output = HashMap::new();
    for page in pages {
        let html = render(&page.body);
        output.insert(page.slug.clone(), html);
    }
    output
}

If the language is omitted or unrecognised, the block is rendered without highlighting. The built-in syntax set covers most common languages including Rust, Python, JavaScript, TypeScript, Go, C, C++, Shell, TOML, YAML, JSON, HTML, CSS, Dockerfile, and many more.

Links whose URL starts with http:// or https:// are rewritten to open in a new tab and carry rel="noopener noreferrer":

[example]https://example.com

renders as:

<a href="https://example.com" target="_blank" rel="noopener noreferrer">example</a>

Relative links, fragment links (#section), and other schemes (mailto:, tel:, …) are left untouched. Wiki Links are always treated as internal.

Images and static files

Images use standard markdown syntax: ![alt](url). For local assets, place files under your static_dir (default static/) and reference them with an absolute path:

![Diagram]/static/images/diagram.png

Here is a rendered example using an image from the wiki's static directory:

A cute aphid

The build step copies static_dir into the output's static/ directory, so the same path works in both serve and build modes. Theme static files are merged into the same /static/ namespace — see Themes for precedence rules.

Extensions

Tables

| Column A | Column B |
|----------|----------|
| cell     | cell     |

Strikethrough

~~deleted text~~

Task lists

- [x] Done
- [ ] Not done

Footnotes

Some text with a footnote.[^1]

[^1]: The footnote content.

Cross-links to any other page by filename stem:

[[page-slug]]
[[page-slug|Display text]]

See Wiki Links for full details.

Not supported

The following are not enabled:

  • Smart punctuation (straight quotes and dashes are left as-is)
  • Math ($...$ / $$...$$)
  • GitHub-style alert blocks (> [!NOTE])
  • Custom heading ID syntax (# Heading {#custom-id}) — IDs are auto-generated from the heading text