Shortcodes are template-based content components you can add without writing Python. Place a Kida template intemplates/shortcodes/and call it from Markdown using Hugo-compatible syntax.
When to Use Shortcodes vs Directives
| Criterion | Shortcode (template) | Directive (Python) |
|---|---|---|
| Implementation | Kida template file | Python class |
| Location | templates/shortcodes/name.html |
bengal/.../directives/builtins/ |
| Validation | Template conditionals only | Regex, typed options |
| Who can add | Content/template authors | Developers |
Choose a shortcode when:
- Output is simple HTML from args (styled blockquote, custom figure)
- No strict validation needed (e.g., "any URL" iframe)
- You want non-developers to add or customize it
- You want to override without touching code
Choose a directive when:
- You need validation (YouTube 11-char ID, Spotify 22-char ID)
- You need computed URLs, privacy mode, or complex options
- You need parent-child nesting (e.g.,
:::{step}inside:::{steps}) - You want structured errors and build-time safety
See Custom Directives for the Python path.
Notation: Markdown vs Standard
Shortcodes support two notations that control how inner content is processed:
| Notation | Example | Inner content |
|---|---|---|
| Standard | {{ < name args > }}...{{ < /name > }} |
Passed raw to template |
| Markdown | {{ % name args % }}...{{ % /name % }} |
Parsed as Markdown first |
Use Markdown notation when inner content should support formatting (e.g.**bold**, [links](url)):
<blockquote class="blockquote"><p>The only way to do great work is to <strong>love</strong> what you do.</p>
<footer>— Jane</footer>
</blockquote>
Use standard notation when inner content should stay as-is (e.g. raw HTML or code):
<pre><code class="language-python">
def hello(): print("world")
</code></pre>
Creating a Shortcode
- Create a template at
templates/shortcodes/<name>.html - Use it in content with
{{ < name args > }}or{{ < name > }}content{{ < /name > }}
Self-Closing Shortcodes
For shortcodes without inner content:
{# templates/shortcodes/audio.html #}
{% set src = shortcode.Get("src") %}
{% if src %}<audio controls preload="auto" src="{{ src }}"></audio>{% end %}
In content:
<audio controls preload="auto" src="/audio/test.mp3"></audio>
Paired Shortcodes
For shortcodes with inner content:
{# templates/shortcodes/blockquote.html #}
<blockquote class="blockquote">
{{- shortcode.Inner -}}
{% set author = shortcode.Get("author") %}
{% if author %}<footer>— {{ author }}</footer>{% end %}
</blockquote>
In content (use {{ % % }}for Markdown in inner content):
<blockquote class="blockquote"><p>The only way to do great work is to <strong>love</strong> what you do.</p>
<footer>— Jane Doe</footer>
</blockquote>
Shortcode Context
Templates receive ashortcodeobject with Hugo-compatible methods:
| Property | Description |
|---|---|
shortcode.Get("key") |
Get named argument |
shortcode.Get(0) |
Get positional argument (0-indexed) |
shortcode.GetInt("key", 0) |
Get argument as int |
shortcode.GetBool("key", false) |
Get argument as bool (true/false, 1/0) |
shortcode.Inner |
Inner content (paired shortcodes only) |
shortcode.InnerDeindent |
Inner with leading indentation stripped |
shortcode.IsNamedParams |
True if named args were used |
shortcode.Params |
All params as dict or list |
shortcode.Ref("path") |
Resolve content path to absolute URL |
shortcode.RelRef("path") |
Resolve content path to relative URL |
shortcode.Parent |
Parent shortcode context when nested (or None) |
You also havepage, site, and config in context. Use page.HasShortcode("name")in layouts to check if a page uses a given shortcode (e.g. to conditionally load CSS).
Arguments
Named arguments:
<figure class="figure">
<img src="/images/cat.jpg" alt="A cat" loading="lazy">
<figcaption>Photo by Jane</figcaption>
</figure>
Positional arguments:
Arguments with spaces must be quoted:
<blockquote class="blockquote">Quote
<footer>— Jane Doe</footer>
</blockquote>
Subdirectory Shortcodes
Organize shortcodes in subdirectories:
templates/shortcodes/
├── media/
│ ├── audio.html
│ └── video.html
└── blockquote.html
Call with the path relative toshortcodes/:
{{< media/audio src=/audio/test.mp3 >}}
Built-in Shortcodes
The default theme ships with these shortcodes. Override any by placing a template with the same name in your project'stemplates/shortcodes/.
| Shortcode | Usage | Purpose |
|---|---|---|
audio |
` |
| HTML5 audio player | |blockquote|
<p>Quote</p>` | Styled blockquote with attribution | | `figure` | `
<img src="/img.jpg" alt="..." loading="lazy">
<figcaption>...</figcaption>
| Image with optional caption and link | |details|
Click
code
| Code block with language class | |param|
| Insert config or frontmatter value | |tip|
Tip
Warning
Danger
| Internal page link (absolute URL) | |relref|
` | Internal page link (relative URL) |
Strict Mode
Setshortcodes.strict: true in bengal.yamlto fail the build when:
- An unknown shortcode is used (no template found)
- A shortcode template fails to render
Useful for CI and catching typos early.
Discoverability
List available shortcodes:
bengal shortcodes list
Shortcode Cookbook
Copy-paste snippets for common patterns.
Conditional CSS in base layout
{% if page.HasShortcode("audio") %}
<link rel="stylesheet" href="/css/audio-player.css">
{% end %}
Nested shortcode with parent
{# templates/shortcodes/img.html — used inside gallery #}
{% set cls = shortcode.Parent.Get("class") if shortcode.Parent else "" %}
<img src="{{ shortcode.Get('src') }}"{% if cls %} class="{{ cls }}"{% end %}>
Internal link with custom text
Typed arguments
{% set cols = shortcode.GetInt("cols", 3) %}
<div class="grid grid-cols-{{ cols }}">
{{ shortcode.Inner }}
</div>
Related
- Custom Directives — Python-based directives with validation
- Theme Customization — Override templates
- Kida Syntax — Template language reference