Shortcodes
Shortcodes let you embed rich, reusable components inside markdown without writing raw HTML. They use square-bracket notation and are expanded before the markdown parser runs.
Inline Shortcodes
Self-closing shortcodes produce output without a body:
[figure src="/img/photo.jpg" alt="A photo" caption="My photo"/]
Renders as:
<figure><img src="/img/photo.jpg" alt="A photo"><figcaption>My photo</figcaption></figure>
Block Shortcodes
Block shortcodes wrap body content between opening and closing tags:
[infobox type="info"] This is an **informational** message with _markdown_ inside. [/infobox]
Renders as:
<div class="infobox infobox-info" role="complementary" aria-label="Info"> <p class="infobox-title">Info</p> This is an <strong>informational</strong> message with <em>markdown</em> inside. </div>
Attribute Syntax
Shortcodes support positional arguments, named key-value pairs, and flags:
[button primary href="/signup"]Get Started[/button] [youtube id="dQw4w9WgXcQ" autoplay/] [details summary="Click to expand"]Hidden content[/details]
| Syntax | Description |
|---|---|
primary | Positional argument (bare word) |
href="/signup" | Named argument (key-value) |
autoplay | Flag (bare word, treated as boolean) |
Named values must be quoted when they contain spaces. Single or double quotes are accepted.
Built-in Shortcodes
Accent CMS ships with these built-in shortcodes:
| Shortcode | Usage | Description |
|---|---|---|
button | [button primary href="/signup"]Label[/button] | Anchor link styled as a button |
infobox | [infobox type="prereq" title="Setup"]...[/infobox] | Rich content box with type, title, and optional collapsibility |
tabs | [tabs group="os"][tab label="macOS"]...[/tab][/tabs] | Tabbed content switcher with group sync |
details | [details summary="Title"]Content[/details] | Collapsible disclosure element |
figure | [figure src="/img/x.jpg" alt="desc" caption="Cap"/] | Figure with image and optional caption |
iframe | [iframe src="demo.html" title="Demo"/] | Embed a page-local or external HTML page |
pdf | [pdf src="report.pdf" title="Q4 Report"/] | PDF download card with thumbnail preview, title, and download link. Falls back to a generic SVG icon when no thumbnail is available |
diagram | [diagram type="mermaid"]flowchart LR; A --> B[/diagram] | Server-render a Mermaid or Svgbob diagram with caption, alt text, and external src support. See the Diagrams guide for the full attribute table, render-mode policy, and an agent-authoring cookbook. |
The [alert] shortcode name is supported as a deprecated alias for [infobox]. Existing content using [alert warning]...[/alert] continues to work.
Iframe Shortcode
The iframe shortcode embeds standalone HTML pages or external URLs in an isolated iframe. This is useful for design system demos, interactive widgets, or third-party content that has its own styles and scripts.
Embed a page-local HTML file:
[iframe src="accent-design.html" title="Design System"/]
When src is a relative path (not starting with http://, https://, or //), it is resolved through the /content-media/ route relative to the current page. For a page at /designsystem, this produces src="/content-media/designsystem/accent-design.html". Place the HTML file in the same content directory as your markdown page.
Embed an external URL:
[iframe src="https://example.com/demo" title="Live Demo" height="800"/]
External URLs pass through unchanged.
Available attributes:
| Attribute | Default | Description |
|---|---|---|
src | (required) | URL or page-local filename to embed |
title | (required) | Accessible title for the iframe |
height | 600 | Height as a CSS value |
width | 100% | Width as a CSS value |
border | 0 | CSS border value |
loading | lazy | lazy or eager loading |
sandbox | (omitted) | Sandbox restrictions (e.g., allow-scripts) |
allow | (omitted) | Permissions policy (e.g., fullscreen) |
Themes can override the built-in iframe by providing shortcodes/iframe.html.jinja in the theme directory.
Embedded HTML and the media CSP
Files served through /content-media/ carry a strict Content-Security-Policy by default (default-src 'none'; sandbox). That is the right policy for images and downloads, but for an embedded HTML page it blocks every subresource – inline <style> blocks, images, fonts, and scripts – so the iframe renders as bare unstyled HTML with no error anywhere.
To make an embedded page work, add a scoped override in config.yaml for exactly the path that needs it:
http_headers: security: routes: - path: "/content-media/designsystem/*" content_security_policy: "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'"
This permits same-origin resources and inline styles for that one page’s media directory while every other /content-media/ response keeps the strict default. If the embedded page needs scripts or external origins, extend the policy for that route only, for example script-src 'self' 'unsafe-inline' or style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com.
Keep overrides as narrow as possible: anything that can upload HTML into a covered content directory (for example the admin media library) is granted the same relaxation. See the configuration reference for matching and precedence details.
Theme Shortcodes
Themes can override built-in shortcodes or define new ones by placing Jinja templates in the shortcodes/ directory:
site/themes/default/shortcodes/
button.html.jinja
alert.html.jinja
custom-widget.html.jinja
Theme shortcode templates receive these context variables:
args.positional– list of positional argumentsargs.named– dictionary of named argumentsbody– the body content (empty string for self-closing shortcodes)
Example theme shortcode template (shortcodes/badge.html.jinja):
{% set variant = args.positional[0] | default("info") %}
<span class="badge badge-{{ variant }}">{{ body }}</span>
Used as: [badge success]Done[/badge]
Plugin Shortcodes
Plugins can also register shortcodes via the [shortcodes] section in plugin.toml. See the Plugins documentation for details.
Resolution Order
When a shortcode is encountered, Accent CMS resolves it through this chain:
- Built-in shortcodes (always available)
- Theme shortcodes (Jinja templates in
shortcodes/) - Plugin shortcodes (registered by WASM plugins)
The first resolver that handles the shortcode wins. This means themes can override built-in shortcodes, and plugins can add entirely new ones.
Unrecognized shortcodes are passed through unchanged so they appear as literal text in the output.