Examples
EN

Shortcodes

Shortcodes are reusable template snippets you can use in Markdown content. Custom shortcodes are Jinja2 templates placed in templates/shortcodes/. See Template Syntax for the templating language reference.

Using Shortcodes

Two syntax patterns work in content files:

{{ shortcode_name(arg1="value", arg2="value") }}

Or explicitly:

{{ shortcode("shortcode_name", arg1="value") }}

Block Shortcode Closers

Block shortcodes support two closer styles:

We strongly recommend named closers ({% endNAME %}) for any non-trivial content. They make your Markdown much easier to read and maintain, especially when mixing multiple shortcodes or using deep nesting.

To keep shortcode syntax literal in a Markdown page outside a code fence, wrap it in a raw block. The {% raw %} and {% endraw %} markers remain visible in Markdown output; fenced blocks inside the region do not split it.

Built-in Shortcodes

Hwaro ships with built-in shortcodes that work out of the box, with no template files needed.

Pass parameters by name or in the shortcode's documented order. For example, {{ youtube("VIDEO_ID") }} and {{ gist("username", "gist_id") }} use positional arguments; quoted empty values still occupy their position.

youtube

Embed a YouTube video.

{{ youtube(id="dQw4w9WgXcQ") }}
{{ youtube(id="dQw4w9WgXcQ", width="800", height="450") }}
{{ youtube(id="dQw4w9WgXcQ", start="30") }}
Param Default Description
id (required) YouTube video ID
width 560 Player width
height 315 Player height
title YouTube Video Accessible title
start Start playback at a given timestamp in seconds

vimeo

Embed a Vimeo video.

{{ vimeo(id="123456789") }}
Param Default Description
id (required) Vimeo video ID
width 560 Player width
height 315 Player height
title Vimeo Video Accessible title

gist

Embed a GitHub Gist.

{{ gist(user="octocat", id="abc123") }}
{{ gist(user="octocat", id="abc123", file="hello.rb") }}
Param Default Description
user (required) GitHub username
id (required) Gist ID
file (none) Specific file to show

alert / callout

Display an alert box. Use as a block shortcode to wrap content.

Both bare and named closers are supported (named closers are recommended for complex pages):

{% alert(type="warning", title="Caution") %}Be careful with this!{% end %}
{% alert(type="tip") %}Using named closer also works{% endalert %}

(We recommend the named closer style for clarity.)

Param Default Description
type info info, warning, danger, tip, success
title (none) Optional title

figure

Image with optional caption.

{{ figure(src="/img/photo.jpg", alt="A photo", caption="My caption") }}
Param Default Description
src (required) Image URL
alt "" Alt text
caption (none) Caption below image
width (none) Image width
height (none) Image height

tweet

Embed a tweet.

{{ tweet(user="jack", id="20") }}
Param Default Description
user (required) Twitter username
id (required) Tweet ID

codepen

Embed a CodePen.

{{ codepen(user="chriscoyier", id="gfdDu") }}
{{ codepen(user="chriscoyier", id="gfdDu", tab="css,result", height="400") }}
Param Default Description
user (required) CodePen username
id (required) Pen ID
tab result Default tab(s)
height 300 Embed height
title CodePen Embed Accessible title

Every parameter marked (required) above lands directly in a URL or an src. Omitting one still renders — into an embed that points nowhere (https://codepen.io//embed/gfdDu) — so the build warns instead of leaving you to find the dead embed on the published page:

[WARN] Shortcode `codepen` is missing required argument `user` — it renders a broken embed. Usage: `{{ codepen(user="…", id="…") }}`.

include_code

Include a source file, or part of it, as a fenced code block. The block goes through the normal code pipeline: highlighting, fence options, the copy button and render-codeblock hooks.

{{ include_code(path="examples/site/config.toml") }}
{{ include_code(path="examples/site/config.toml", region="feeds", lines="2-8", title="config.toml", hl_lines="3") }}
Param Default Description
path (required) File path, relative to the project root
region (none) Only the lines between #region <name> and #endregion <name>
lines (none) Line range a-b or a single line a (1-based, inclusive; counted inside region when both are set)
lang from the file name or extension (Makefile, Dockerfile, .rs); in client highlight mode, as highlight.js names it Language of the code block
title, hl_lines, hide_lines, linenos, linenostart, copy Passed on as fence options

Region markers can follow any comment leader, so they stay valid code in the source file:

module App
  # #region server
  def run
    serve
  end
  # #endregion server
end

//, #, --, <!--, /* and ; all work, and a bare #endregion closes the innermost open region. The markers, and any nested markers inside the selected region, are left out of the output. Without region, the file is included as it is. Common leading indentation is removed.

include_md

Render a Markdown file in place, as if its text were written at that spot.

{{ include_md(path="shared/install.md") }}
{{ include_md(path="shared/install.md", region="brew") }}
Param Default Description
path (required) File path, relative to the project root
region (none) Only the lines between <!-- #region <name> --> and <!-- #endregion <name> -->

Shortcodes inside the included file expand, and its headings take part in the page's table of contents and heading ids. Its front matter is dropped. An included file can include others, up to 8 levels deep. A cycle is an error that shows the chain.

Paths and errors

Both shortcodes read files under the project root only. Absolute paths, .., symlinks that resolve outside the project, the build output directory and .hwaro/ are refused. A missing file or region, a line range outside the file, or a refused path fails the build with an error that names the page and the file:

Error [HWARO_E_TEMPLATE]: {{ include_code(path="examples/app.cr", region="srv") }} in content/guide.md: region 'srv' not found

An included file is a dependency of the page that includes it. A warm --cache build and hwaro serve both re-render the page when the file changes, including files outside content/.

A call inside a code span, a fenced code block or a {% raw %} region stays literal.

To override any built-in shortcode, create a file with the same name in templates/shortcodes/ (e.g., templates/shortcodes/youtube.html). User templates always take priority — and define their own contract, so the warning above does not apply to them.

Creating Custom Shortcodes

Shortcode templates live in templates/shortcodes/.

A shortcode template that fails while rendering (for example, an undefined attribute access or an {% include %} of a missing template) fails the build with HWARO_E_TEMPLATE (exit code 4), just like a page template. The error names the shortcode file and line, such as templates/shortcodes/alert.html:3:5. Under hwaro serve it appears in the error overlay instead.

A syntax error in a shortcode template is reported as a warning with the same file and location. Hwaro places a visible HTML comment at the call and keeps rendering the page.

Example: Alert Box

Create templates/shortcodes/alert.html:

{% if type and message %}
<div class="alert alert-{{ type }}">
  {{ message | safe }}
</div>
{% endif %}

Use in content:

{{ alert(type="warning", message="This is important!") }}

Output:

<div class="alert alert-warning">
  This is important!
</div>

Example: YouTube Embed

Create templates/shortcodes/youtube.html:

{% if id %}
<div class="video-container">
  <iframe 
    src="https://www.youtube.com/embed/{{ id }}"
    frameborder="0"
    allowfullscreen>
  </iframe>
</div>
{% endif %}

Use in content:

{{ youtube(id="dQw4w9WgXcQ") }}

Example: Figure with Caption

Create templates/shortcodes/figure.html:

<figure>
  <img src="{{ src }}" alt="{{ alt | default(value='') }}">
  {% if caption %}
  <figcaption>{{ caption }}</figcaption>
  {% endif %}
</figure>

Use in content:

{{ figure(src="/images/photo.jpg", alt="A photo", caption="My caption") }}

You can create a gallery that automatically lists images found in the same directory as the page (Page Bundle).

Create templates/shortcodes/gallery.html:

<div class="gallery">
{% for asset in page.assets -%}
  {%- if asset is matching("[.](jpg|png)$") -%}
    {% set image = resize_image(path=asset, width=240, height=180) %}
    <a href="{{ get_url(path=asset) }}" target="_blank">
      <img src="{{ image.url }}" alt="{{ asset }}" />
    </a>
  {%- endif %}
{%- endfor %}
</div>

Use in content (inside a Page Bundle directory):

{{ gallery() }}

This will render a grid of all JPG and PNG images found alongside the Markdown file.

Block Shortcodes

Block shortcodes wrap content between opening and closing tags:

{% note() %}
This is the **body** content of the shortcode.
{% end %}

The body is passed to the shortcode template as the body variable. Markdown conversion is not applied automatically, so use the markdownify filter in your template if needed:

<div class="note">
  {{ body | markdownify | safe }}
</div>

Or use the body as-is for raw content:

<div class="note">{{ body }}</div>

Nested Shortcodes

Block shortcodes can be nested up to 5 levels deep:

{% outer() %}
  Some text with {{ inner(type="info") }} inside.
{% end %}

Argument Syntax

Named Arguments

Arguments support multiple quote styles:

{{ alert(type="warning", message="Double quotes") }}
{{ alert(type='info', message='Single quotes') }}
{{ alert(type=danger, message=Unquoted) }}

An unquoted value ends at the first space (message=No quotes here passes just No), so quote anything longer than one word.

Positional Arguments

When no key=value syntax is used, arguments are assigned as _0, _1, etc.:

{{ youtube("dQw4w9WgXcQ") }}

In the shortcode template, access via {{ _0 }}:

<iframe src="https://www.youtube.com/embed/{{ _0 }}"></iframe>

Built-in Variables

Shortcodes have access to:

{# In shortcode template #}
<a href="{{ site.base_url }}/{{ url }}">{{ text }}</a>

Tips

Validate Arguments

Always check for required arguments:

{% if not url %}
<p class="error">Missing url parameter</p>
{% else %}
<a href="{{ url }}">{{ text | default(value="Link") }}</a>
{% endif %}

Use Safe Filter for HTML

When passing HTML content:

{{ content | safe }}

Organize Shortcodes

Group related shortcodes:

templates/shortcodes/
├── alert.html
├── youtube.html
├── figure.html
├── code/
│   ├── tabs.html
│   └── snippet.html
└── social/
    ├── twitter.html
    └── github.html

See Also