Markdown Extensions

Accent CMS extends CommonMark with a rich set of optional markdown extensions powered by pulldown-cmark. All extensions are enabled by default and can be toggled individually via config.yaml.

Overview

ExtensionConfig KeyDefaultDescription
TablestablestrueGFM pipe-delimited tables
FootnotesfootnotestrueReference-style footnotes
Strikethroughstrikethroughtrue~~deleted text~~
Task Liststaskliststrue- [x] checkbox lists
Heading Attributesheading_attributestrue# Title { .class #id }
Wiki Linkswikilinkstrue[[Page Name]] links
Smart Punctuationsmart_punctuationtrueCurly quotes, dashes, ellipsis; locale-aware quote forms
Mathmathtrue$inline$ and $$display$$ LaTeX
Definition Listsdefinition_liststrueterm / : definition pairs
GFM ExtrasgfmtrueBlock-quote alerts and other GFM features
AdmonitionsadmonitionstrueStyled > [!NOTE] callouts
Superscriptsuperscripttrue^text^
Subscriptsubscripttrue~text~
Metadata Blocksmetadata_blockstrueYAML (---) and TOML (+++) frontmatter

Extensions

Tables

Standard GFM pipe-delimited tables with alignment support.

| Left | Center | Right |
|:-----|:------:|------:|
| A    |   B    |     C |

Footnotes

Reference-style footnotes appear at the bottom of the rendered page.

This claim needs a source[^1].

[^1]: Author, *Title*, Publisher, 2024.

Strikethrough

Wrap text in double tildes to strike it through.

~~deleted text~~

Renders as: deleted text

Important: When subscript is also enabled (the default), single tilde ~text~ produces subscript, not strikethrough. Use double tildes ~~text~~ for strikethrough. See the Subscript section below.

Task Lists

Interactive checkbox lists for to-do items.

- [x] Write documentation
- [ ] Review pull request
- [ ] Deploy to production

Heading Attributes

Add custom CSS classes, IDs, and attributes to headings.

# My Heading { .custom-class #custom-id }
## Another { data-section="intro" }

Wiki-style links for linking between pages without knowing the full URL path.

Basic Syntax

[[Getting Started]]

Links to the page whose title matches “Getting Started”. The link text defaults to the page title.

Display Text

[[Getting Started|click here to begin]]

Links to “Getting Started” but displays “click here to begin” as the link text.

Heading Anchors

[[Getting Started#installation]]

Links to the #installation heading on the “Getting Started” page.

Image Embeds

![[screenshot.png]]
![[screenshot.png|Screenshot of the dashboard]]

Embeds an image. The text after | becomes the alt text.

Resolution Algorithm

When Accent encounters [[Page Name]], it resolves the link in this order:

  1. Title match – find a page whose title frontmatter exactly matches “Page Name” (case-insensitive)
  2. Slug match – find a page whose URL slug matches the slugified form
  3. Relative path – look for a sibling or child page with a matching name
  4. Absolute path – treat the text as an absolute URL path
  5. Broken link – if no match is found, render with a wikilink-broken CSS class

Troubleshooting

  • Ambiguous matches: If multiple pages share the same title, the first match wins. Use a more specific reference like [[docs/Getting Started]].
  • Broken links: Run cargo run -- validate to find broken wiki links across your site.
  • Case sensitivity: Title matching is case-insensitive, but slug matching uses the exact slugified form.

Every page exposes a page.backlinks template variable containing a list of pages that link to it via wiki links. Use this in templates to build “What links here” sections:

{% if page.backlinks | length > 0 %}
<h2>Pages linking here</h2>
<ul>
  {% for link in page.backlinks %}
  <li><a href="{{ link.url }}">{{ link.title }}</a></li>
  {% endfor %}
</ul>
{% endif %}

Backlinks are available in accent build and dev serve with no license; serving in production requires a Standard or Pro license.

Smart Punctuation

Automatically converts ASCII punctuation to typographic equivalents.

InputOutput
"quotes"“smart quotes”
'quotes'‘smart quotes’
--– (en dash)
---— (em dash)
...… (ellipsis)

Locale-Dependent Quote Forms

Curly-quote characters are remapped to the typographic conventions of the page’s locale. pulldown-cmark always emits English curly quotes ("hello" -> “hello”); a post-processor then rewrites them to the locale’s preferred forms.

LocaleOpen doubleClose doubleOpen singleClose singleExample
en (default)“hello”
de, cs„hello“
de-CH«»«hello»
fr, fr-FR, fr-CA, fr-CH«»«hello»
pl»«„hello”
sv, fi”hello”
ja, zh-TW, zh-HK「hello」
zh, zh-CN, ko“hello”

The table above is a sample. The full set of 52 BCP-47 locales is resolved at runtime from a canonical smart-quote spec – the single source of truth shared by the renderer and editorial tooling.

The locale is resolved per page in this order:

  1. locale: <code> in the page’s frontmatter (highest priority – useful for multilingual sites mixing languages on a single page).
  2. markdown.locale in config.yaml (site-wide override).
  3. The page’s language field, set from filename suffix (e.g. default.de.md) or site.language.
  4. en as the final fallback.

BCP-47 tag lookup follows three steps in order, stopping at the first hit: (1) exact tag (de-CH resolves to Swiss German guillemets); (2) language-only (de-LI strips its region and resolves to mainland de); (3) fallback locale (unknown tags fall back to en). Underscore separators (de_AT) are accepted as well. Script and variant subtags (zh-Hans, de-1996) collapse to the language part. Dashes and ellipsis are locale-independent and pass through unchanged.

Apostrophes in Contractions

The apostrophe and the closing single quote share the same codepoint (U+2019). A context-sensitive heuristic distinguishes them per Event::Text run: when U+2019 sits between two Unicode letters (don't, Alice's, c'est, l'enfant) it is preserved verbatim as the canonical apostrophe; only U+2019 with a letter on the left and a non-letter on the right is treated as a closing single quote and remapped to the locale’s close-single glyph. The heuristic is conservative – ambiguous positions (e.g. '89 year abbreviations) preserve the apostrophe rather than mis-rendering it as a locale-specific quote.

# config.yaml -- force German conventions site-wide
markdown:
  smart_punctuation: true
  locale: de
---
title: Bonjour le monde
locale: fr
---

"Voila!" she said.

The example above renders as «\u{00A0}Voila!\u{00A0}» she said. regardless of the surrounding site language. The character between the guillemets and the quoted text is U+00A0 (non-breaking space), not an ASCII space (see Inner-Guillemet Spacing below).

Inner-Guillemet Spacing

French and Swiss French typography pads the inside of guillemets with a non-breaking space. The locale’s default is taken from the canonical spec:

LocaleInner spaceCodepointExample
fr, fr-FR, fr-CANBSPU+00A0«\u{00A0}texte\u{00A0}»
fr-CHNARROW NO-BREAK SPACEU+202F«\u{202F}texte\u{202F}»
de-CH, it, es, pt, ru, el (other guillemet locales)none«hello»

The injected character is non-breaking, so the quote and the quoted text never separate at a line break. The space is also applied to the single chevrons and used as nested-quote pairs in French.

Canonical Locale Spec (52 BCP-47 locales)

The locale-to-quote mapping is loaded at runtime from a canonical spec file that covers 52 BCP-47 locales – including region-tagged variants (de-CH, fr-CA, pt-BR), inner-guillemet spacing defaults (NBSP, thin space), nested-quote handling for Polish and Hungarian, and RTL marking for Arabic and Hebrew. The spec set consists of:

  • a machine-readable JSON spec – single source of truth
  • a JSON Schema – validated in CI
  • a human-readable reference table and codepoint cheat sheet
  • editorial rules for AI assistants generating prose
Deployment-Level Overrides

A site can override any spec value without forking the JSON. Two knobs sit under markdown.smart_quotes: inner_space (default and per-locale spacing) and locales (wholesale quote-character replacement).

# config.yaml
markdown:
  smart_punctuation: true
  locale: de
  smart_quotes:
    inner_space:
      default: thin          # flip every locale to thin spaces
      per_locale:
        fr-FR: nbsp           # but keep NBSP for fr-FR
        de-CH: none           # and remove spacing from Swiss German
    locales:
      # Editorial style guide for a Norwegian site that prefers curly
      # doubles over the spec's guillemets.
      nb:
        double: { open: "\u{201C}", close: "\u{201D}" }
        single: { open: "\u{2018}", close: "\u{2019}" }

Resolution priority (highest first) for inner spacing:

  1. markdown.smart_quotes.inner_space.per_locale[<tag>] – exact canonical match
  2. markdown.smart_quotes.inner_space.per_locale[<language>] – language-only fallback (a per_locale: { de: ... } entry covers de-CH, de-AT, …)
  3. markdown.smart_quotes.inner_space.default – site-wide knob
  4. Spec’s locales[<tag>].innerSpace
  5. Spec’s innerSpace.globalDefault

The default knob is intentionally stronger than the spec’s per-locale value: a single line in config.yaml can enforce thin spaces across every guillemet locale.

Wholesale locales overrides follow the same exact -> language path. Only double and single are replaced; inner spacing stays at the spec’s value unless an inner_space knob is also configured (the two knobs compose). Nested-quote behaviour and writing direction always come from the spec.

Locale tags are validated at config load. Unknown tags (qq-ZZ) are rejected with a clear error message rather than silently falling through to the language fallback. Region-only fall-throughs are accepted: de-LI validates because de is in the spec.

Changes are picked up by pkill -HUP accent (or POST /_admin/reload) without restarting the server.

Math

LaTeX-style math expressions rendered via KaTeX.

Inline math uses single dollar signs:

The quadratic formula is $x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}$.

Display math uses double dollar signs:

$$
\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}
$$

Theme Integration

Accent automatically includes KaTeX CSS and JS on pages that contain math expressions (detected via the page.has_math template variable). The default theme’s base.html.jinja loads the KaTeX CDN resources conditionally:

{% if page.has_math %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/contrib/auto-render.min.js"
    onload="renderMathInElement(document.body, {
      delimiters: [
        {left: '$$', right: '$$', display: true},
        {left: '$', right: '$', display: false}
      ]
    })"></script>
{% endif %}

If you use a custom theme, add similar KaTeX loading to your base template.

Definition Lists

Term-definition pairs useful for glossaries and reference material.

Accent CMS
: A high-performance markdown CMS written in Rust.

pulldown-cmark
: The CommonMark parser library used by Accent.

Renders as a <dl> with <dt> and <dd> elements.

Admonitions

GFM-style block-quote alerts rendered as styled callout boxes. Five types are available:

> [!NOTE]
> Useful background information the reader should know.

> [!TIP]
> Helpful advice for getting the most out of a feature.

> [!IMPORTANT]
> Key information the reader must not miss.

> [!WARNING]
> Potential issues that could cause problems.

> [!CAUTION]
> Actions that could lead to data loss or security issues.

Each type renders with a distinct color and icon. The styling is defined in the theme CSS and can be customized.

When to use admonitions vs. infoboxes: Admonitions are standard GFM syntax that renders in GitHub, Obsidian, and other tools. Use them for portable “pay attention” callouts. For Accent-specific features like custom types, titles, or collapsible sections, use the [infobox] shortcode instead.

Superscript

Wrap text in carets for superscript.

E = mc^2^

Renders as: E = mc2

Subscript

Wrap text in single tildes for subscript.

H~2~O is the chemical formula for water.

Renders as: H2O

Tilde semantic change: When subscript is enabled (default), single tilde ~text~ produces subscript instead of strikethrough. Double tilde ~~text~~ always produces strikethrough regardless of the subscript setting. If you need single-tilde strikethrough behavior (rare), disable subscript in your config.

Shortcodes for Structured Content

Beyond markdown extensions, Accent CMS provides shortcodes for structured content components.

Infobox

The [infobox] shortcode creates rich content boxes with custom types, titles, and optional collapsibility:

{% infobox type=“prereq” title=“Before You Begin” %} Make sure you have Rust 1.75+ installed and cargo available in your PATH. {% /infobox %}

{% infobox type=“version” title=“Tilde Behavior Change” collapsible=true %} When subscript is enabled (the default), single tilde ~text~ produces subscript instead of strikethrough. Use double tildes ~~text~~ for strikethrough. {% /infobox %}

Available types: info (default), prereq, api, security, version. Custom types are also supported.

Tabs

The [tabs] shortcode presents parallel alternatives in a tabbed interface:

{% tabs group=“os” %} {% tab label=“macOS” %} Install via Homebrew:

brew install accent

{% /tab %} {% tab label=“Linux” %} Install via the install script:

curl -fsSL https://accentcms.dev/install.sh | sh

{% /tab %} {% tab label=“Windows” %} Install via winget:

winget install accent

{% /tab %} {% /tabs %}

Tabs with the same group value synchronize across the page. The selected tab persists across page loads via localStorage.

Configuration

All extensions are configured under the markdown: key in config.yaml. Every option defaults to true.

markdown:
  tables: true
  footnotes: true
  strikethrough: true
  tasklists: true
  heading_attributes: true
  wikilinks: true
  smart_punctuation: true
  math: true
  definition_lists: true
  gfm: true
  superscript: true
  subscript: true
  admonitions: true
  metadata_blocks: true

To disable a specific extension, set it to false:

markdown:
  smart_punctuation: false  # Keep ASCII quotes
  subscript: false          # Restore single-tilde strikethrough

Omitted keys default to true, so you only need to list the extensions you want to change.

Migration Guides

From Obsidian

Obsidian and Accent share several markdown features. Here is what works out of the box and what needs adjustment.

Works directly:

  • Wiki links [[Page Name]] with display text [[Page|text]]
  • Image embeds ![[image.png]]
  • Math with $ and $$ delimiters
  • Footnotes with [^label] syntax
  • Task lists with - [x] syntax

Requires adjustment:

  • Callouts: Obsidian uses > [!note] (lowercase). Accent supports both > [!NOTE] (GFM standard, uppercase) and > [!note] (case-insensitive matching). Most callouts work as-is.
  • Tags: Obsidian uses #tag inline tags. Accent uses YAML frontmatter tags. Move tags to the frontmatter tags: field.
  • Internal links: Obsidian resolves [[Page]] by filename. Accent resolves by page title first, then slug. If your filenames differ from titles, some links may need updating.
  • Embeds: Obsidian’s ![[note]] for embedding other notes is not supported. Only image embeds work in Accent.

From Hugo

Hugo and Accent both support CommonMark with extensions, but the configuration and some syntax differs.

Works directly:

  • Standard markdown (headings, lists, links, images, code blocks)
  • Tables, footnotes, strikethrough, task lists
  • Definition lists

Requires adjustment:

  • Shortcodes: Hugo shortcodes use {{< name >}} syntax. Accent uses [name]...[/name]. Rewrite shortcodes to Accent’s bracket syntax.
  • Front matter: Hugo uses TOML (+++) or YAML (---). Accent uses YAML frontmatter. Convert TOML frontmatter to YAML.
  • Content organization: Hugo uses content/ with _index.md for section pages. Accent uses numbered folders (01.section/default.md).

From Pandoc

Pandoc’s extended markdown has significant overlap with Accent’s extensions.

Works directly:

  • Definition lists (same syntax)
  • Superscript ^text^ and subscript ~text~ (same syntax)
  • Math with $ and $$ delimiters
  • Footnotes (same syntax)
  • Tables (GFM style)

Requires adjustment:

  • Smart quotes: Pandoc enables smart quotes with --smart. Accent enables them by default via smart_punctuation: true.
  • Heading attributes: Pandoc uses {#id .class key=value}. Accent supports the same syntax.
  • Citation syntax: Pandoc’s [@citation] is not supported. Use footnotes or manual references.
  • Raw blocks: Pandoc’s ```{=html} raw blocks are not supported. Use standard HTML blocks instead.