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
| Extension | Config Key | Default | Description |
|---|---|---|---|
| Tables | tables | true | GFM pipe-delimited tables |
| Footnotes | footnotes | true | Reference-style footnotes |
| Strikethrough | strikethrough | true | ~~deleted text~~ |
| Task Lists | tasklists | true | - [x] checkbox lists |
| Heading Attributes | heading_attributes | true | # Title { .class #id } |
| Wiki Links | wikilinks | true | [[Page Name]] links |
| Smart Punctuation | smart_punctuation | true | Curly quotes, dashes, ellipsis; locale-aware quote forms |
| Math | math | true | $inline$ and $$display$$ LaTeX |
| Definition Lists | definition_lists | true | term / : definition pairs |
| GFM Extras | gfm | true | Block-quote alerts and other GFM features |
| Admonitions | admonitions | true | Styled > [!NOTE] callouts |
| Superscript | superscript | true | ^text^ |
| Subscript | subscript | true | ~text~ |
| Metadata Blocks | metadata_blocks | true | YAML (---) 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 Links
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:
- Title match – find a page whose
titlefrontmatter exactly matches “Page Name” (case-insensitive) - Slug match – find a page whose URL slug matches the slugified form
- Relative path – look for a sibling or child page with a matching name
- Absolute path – treat the text as an absolute URL path
- Broken link – if no match is found, render with a
wikilink-brokenCSS 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 -- validateto find broken wiki links across your site. - Case sensitivity: Title matching is case-insensitive, but slug matching uses the exact slugified form.
Backlinks
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.
| Input | Output |
|---|---|
"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.
| Locale | Open double | Close double | Open single | Close single | Example |
|---|---|---|---|---|---|
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:
locale: <code>in the page’s frontmatter (highest priority – useful for multilingual sites mixing languages on a single page).markdown.localeinconfig.yaml(site-wide override).- The page’s
languagefield, set from filename suffix (e.g.default.de.md) orsite.language. enas 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:
| Locale | Inner space | Codepoint | Example |
|---|---|---|---|
fr, fr-FR, fr-CA | NBSP | U+00A0 | «\u{00A0}texte\u{00A0}» |
fr-CH | NARROW NO-BREAK SPACE | U+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:
markdown.smart_quotes.inner_space.per_locale[<tag>]– exact canonical matchmarkdown.smart_quotes.inner_space.per_locale[<language>]– language-only fallback (aper_locale: { de: ... }entry coversde-CH,de-AT, …)markdown.smart_quotes.inner_space.default– site-wide knob- Spec’s
locales[<tag>].innerSpace - 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
#taginline tags. Accent uses YAML frontmatter tags. Move tags to the frontmattertags: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.mdfor 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 viasmart_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.