Shortcodes
Shortcodes are small Liquid templates you call from Markdown content. They are compatible with Hugo shortcodes, and add parameter schemas, list and map arguments, ::: directives and a template tag for layouts.
Calling a shortcode
{{< audio src="/audio/intro.mp3" >}}
{{< note >}}
Some **important** text.
{{< /note >}}
There are two notations:
| Notation | Output |
|---|---|
{{< name >}} |
HTML, inserted after Markdown is rendered and never wrapped in <p> |
{{% name %}} |
Markdown, inserted before Markdown is rendered, so it goes through Markdig with the rest of the page |
A shortcode whose template uses inner needs a closing tag ({{< /note >}}) or the self-closing form ({{< note />}}). Shortcodes nest, and a nested shortcode renders first, so its parent receives the rendered output as inner.
Arguments
Arguments are separated by spaces and may span several lines. Pass them by name or by position, but not both (unless the template declares a schema):
{{< figure src="cat.jpg" alt="A cat" width=300 >}}
{{< youtube dQw4w9WgXcQ >}}
| Form | Value |
|---|---|
"text", 'text' |
a string; \" escapes a double quote |
`raw` |
a raw string, which may span lines |
true, false, 42, 1.5 |
a boolean or a number when unquoted |
["a", "b"] |
a list |
{w: 300, h: 200} |
a map |
To show a shortcode literally, comment it out: {{</* name */>}} renders as {{< name >}}.
Writing a shortcode
Create shortcodes/<name>.html in your site or in your theme. A site file overrides a theme file of the same name, and both override the built-ins. Names may include folders: {{< media/audio >}} uses shortcodes/media/audio.html.
<audio controls preload="auto" src="{{ src }}" title="{{ page.Title }}"></audio>
Inside the template:
| Variable | Content |
|---|---|
page, site, fanstatic |
the calling page, its site and Fanstatic info |
| each named argument | {{ src }} for src="…" |
inner |
the content between the opening and closing tags |
params |
the arguments: a map for named ones (params.src), a list for positional ones (params[0]) |
shortcode.Name, shortcode.Params, shortcode.IsNamedParams |
the call |
shortcode.Inner, shortcode.InnerDeindent |
the inner content, as is or without its common indentation |
shortcode.Ordinal, shortcode.Parent |
the position among sibling calls and the enclosing shortcode |
shortcode.Position |
the call location as file:line:col |
Helpful filters:
{{ inner | markdownify }}renders Markdown, dropping the<p>of a single paragraph;markdownify: "block"keeps it.{{ "src is required" | errorf }}fails the build with the call location;{{ "deprecated: %s" | warnf: name }}logs a warning.
page.Shortcodes lists the shortcodes a page calls, so a layout can load assets only where needed:
{% if page.Shortcodes contains "audio" %}<link rel="stylesheet" href="/css/audio.css">{% endif %}
Language and output format variants
Within each folder, the most specific file wins:
name.<lang>.<format>.<ext>, e.g.audio.pt-br.rss.xmlname.<format>.<ext>, e.g.audio.rss.xmlname.<lang>.<ext>, e.g.audio.pt-br.htmlname.<ext>, e.g.audio.html
Output formats other than HTML fall back to the HTML templates.
Parameter schemas
A template may start with YAML front matter declaring its parameters. Fanstatic then validates every call at build time, with the call location, and applies defaults:
---
Positional: [src, width]
Params:
src: { Type: string, Required: true }
width: { Type: int, Default: 300 }
kind: { Type: string, Allowed: [note, warn] }
---
<img src="{{ src }}" width="{{ width }}" class="{{ kind }}">
Types are string, int, float, bool, list, map and any. Positional names the positional arguments, so {{< card "a.jpg" kind="warn" >}} is valid.
Directives
A Docusaurus-style container directive calls the shortcode of the same name in Markdown notation, with the text after the attributes as title:
:::note{kind=warn} Read this first
Directive **content** becomes `inner`.
:::
Longer fences (::::) nest. When no shortcode has that name, the block is rendered as a Markdig custom container, as before.
Shortcodes in layouts
Layouts call shortcodes with the shortcode tag. Arguments are Liquid expressions:
{% shortcode 'audio', src: page.Params.audio %}
{% shortcode 'note' %}
Written by **{{ page.Params.author }}**.
{% endshortcode %}
The block form is used when the shortcode uses inner. Unlike {% render 'shortcodes/audio' %}, the tag follows the language and format lookup, site overrides and schemas, and also reaches the built-ins.
Built-in shortcodes
| Shortcode | Arguments |
|---|---|
ref, relref |
path (with an optional #anchor), lang, outputFormat |
details |
summary, open, class, name, title; inner content |
figure |
src (a page resource first), link, target, rel, alt, title, caption, attr, attrlink, class, width, height, loading |
highlight |
language, options such as "linenos=table,hl_lines=2 4-5,linenostart=10"; inner code |
instagram |
post id |
param |
a dotted key read from the page params, then the site params |
qr |
text or inner content, level, scale, alt, class, title, loading |
vimeo |
id, class, title, allowFullScreen, loading |
x |
user, id; fetched once through oEmbed and cached |
youtube |
id, start, end, autoplay, mute, loop, controls, allowFullScreen, class, title, loading |
highlight only writes markup: a fenced code block with language-*, line-numbers and data-line attributes for a client-side highlighter such as Prism or highlight.js. qr embeds the PNG in the page, youtube uses youtube-nocookie.com, and vimeo sets dnt=1. When x cannot reach the network, it renders a link to the post and logs a warning.
Errors
An unknown shortcode, a syntax error, a schema violation or a template failure is reported with its location and fails the build:
[ERR] 'content/blog/post.md:12:1': shortcode 'audoi' not found
{{< audoi src="/a.mp3" >}}
While fanstatic serve is running, the error is shown in the browser instead of the page.