Template Shortcodes

Create template-only embeds without writing Python

5 min read 986 words

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">&lt;p&gt;The only way to do great work is to &lt;strong&gt;love&lt;/strong&gt; what you do.&lt;/p&gt;

  <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(&quot;world&quot;)
</code></pre>

Creating a Shortcode

  1. Create a template attemplates/shortcodes/<name>.html
  2. 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">&lt;p&gt;The only way to do great work is to &lt;strong&gt;love&lt;/strong&gt; what you do.&lt;/p&gt;

  <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>
— Name
` | Styled blockquote with attribution | | `figure` | `
<img src="/img.jpg" alt="..." loading="lazy">


  <figcaption>...</figcaption>

| Image with optional caption and link | |details|

Click
<p>Content</p>
` | Collapsible `
` | | `highlight` | `
code

| Code block with language class | |param|

| Insert config or frontmatter value | |tip|

Tip

<p>Hint</p>
` | Tip callout box | | `warning` | `

Warning

<p>Caution</p>
` | Warning callout box | | `danger` | `

Danger

<p>Critical</p>
` | Danger callout box | | `ref` | `

docs/guide/intro

| Internal page link (absolute URL) | |relref|

docs/guide/intro

` | 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 %}>




Typed arguments

{% set cols = shortcode.GetInt("cols", 3) %}
<div class="grid grid-cols-{{ cols }}">
  {{ shortcode.Inner }}
</div>