Hugo Shortcodes
- aside shortcode
- baseline-status shortcode
- captioned-image shortcode
- code shortcode
- br shortcode
- rawhtml shortcode
Default markdown is fine for text and images, but for web frontend heavy articles, I’d like to have interactive code snippets and demos. It’s also important to me that the site layout is responsive to small and large screens, offering the best experience int both. Hugo shortcodes are potentially a clean way to do this, as I can maintain the HTML and CSS for each custom-markdown feature separate from the content of blog posts.
So far, I’m using these shortcodes
- aside - A visually emphasized block of text with an icon, header, and body text. Currently the three types of asides are “tip”, “best-practice”, and “none” (a sort of catch-all for any tangential thought I want to share, or use to break up large swaths of text.)
- baseline-status - A quick and easy way to embed baseline-status.
- captioned-image
- code - At the moment I’m using this as a way to wrap a code block in HTML
<details>
and<summary>
tags with some custom styling for animated expand/collapse. Eventually, I’d like a “copy to clipboard” button, custom JS to colorize hex color constants, optionally include a filename or language type in a header section, optionally show line numbers, tabs to toggle between the code and a demo/screenshot/video, support for multiple code file tabs (e.g. it’s fairly common to share an example that has an HTML, CSS, and JavaScript file), and more. - br - This one’s pretty silly but it solved my problem in 30 seconds and I’m not looking back until it warrants more investigation.
- rawhtml - As an escape hatch, I can embed arbitrary html from markdown in the statically generated html. I try not to use this because it could quickly get out of hand with dozens of slightly-different variants of a given component that I have to maintain against years of posts.
aside shortcode
aside.html
{{ if not (.Get "type") }}
{{ errorf "The %q shortcode requires a 'type' argument equal to one of 'none' | 'tip' | 'best-practice'. See %s" .Name
.Position }}
{{ end }}
{{ if not (.Get "title") }}
{{ errorf "The %q shortcode requires a 'title' argument. See %s" .Name .Position }}
{{ end }}
<aside data-type="{{ .Get "type" }}">
<span class="header">
<div class="icon" style="mask: url(/images/aside-{{ .Get "type" }}.svg)"></div>
<h1>{{ .Get "title" }}</h1>
</span>
{{ if eq (.Get "innertype") "markdown" }}
<div>{{ .Inner | markdownify }}</div>
{{ else }}
<div>{{ .Inner }}</div>
{{ end }}
</aside>
_shortcode-aside.scss
.content {
aside {
--aside-bg: var(--aside-none-bg);
--aside-border: var(--aside-none-border);
--aside-icon-url: '/images/aside-none.svg';
color: var(--aside-fg-color);
background-color: var(--aside-bg);
border-left: 3px solid var(--aside-border);
padding: 2rem;
margin: 1rem 0;
.header {
.icon {
display: inline-block;
background: var(--aside-fg-color);
width: 2.4rem;
height: 2.4rem;
margin-bottom: -2px; // hack
}
h1 {
color: var(--aside-fg-color);
display: inline;
font-size: 1.8rem;
margin-block-start: 0;
margin-block-end: 0;
}
}
}
aside[data-type="tip"] {
--aside-bg: var(--aside-tip-bg);
--aside-border: var(--aside-tip-border);
--aside-icon-url: '/images/aside-tip.svg';
}
aside[data-type="best-practice"] {
--aside-bg: var(--aside-best-practice-bg);
--aside-border: var(--aside-best-practice-border);
--aside-icon-url: '/images/aside-best-practice.svg';
}
}
Relevant color variables in _variables.scss
:root {
// [...]
--aside-fg-color: #212121;
--aside-none-bg: #eaf0fb;
--aside-none-border: #4134f5;
--aside-best-practice-bg: #eef8f4;
--aside-best-practice-border: #417f3e;
--aside-tip-bg: #f8f8ee;
--aside-tip-border: #fff700;
// [...]
@media only screen and (prefers-color-scheme: dark) {
// [...]
--aside-fg-color: #fff;
--aside-none-bg: #243a64;
--aside-none-border: #4134f5;
--aside-tip-bg: #085335;
--aside-tip-border: #417f3e;
--aside-best-practice-bg: #484800;
--aside-best-practice-border: #fff700;
// [...]
}
}
Static files
Usage
{{< aside type="best-practice" title="Clearly user-defined" >}}
Prefix your <custom-ident> values with a double-hyphen to make it abundantly clear that the identifier is user-defined.
{{< /aside >}}
Result
baseline-status shortcode
baseline-status.html
{{ if not (.Get "featureId") }}
{{ errorf "The %q shortcode requires a 'featureId' argument. See %s" .Name .Position }}
{{ end }}
<baseline-status featureId="{{ .Get "featureId" }}"></baseline-status>
config.toml (perhaps unnecessary indirection)
baselineStatusWebComponentScriptUrl = "script/baseline-status.min.js"
themes/hugo-coder/layouts/_default
// at the end of <head>
<script src="{{ .Site.Params.baselineStatusWebComponentScriptUrl | relURL }}" type="module"></script>
Static files
Usage
{{< baseline-status featureId="view-transitions" >}}
Result
captioned-image shortcode
captioned-image.html
{{ if not (.Get "src") }}
{{ errorf "The %q shortcode requires a 'src' argument. See %s" .Name .Position }}
{{ end }}
{{ if not (.Get "alt") }}
{{ errorf "The %q shortcode requires an 'alt' argument. See %s" .Name .Position }}
{{ end }}
{{ if not (.Get "caption") }}
{{ errorf "The %q shortcode requires a 'caption' argument. See %s" .Name .Position }}
{{ end }}
<figure class="captioned-image">
<img src="{{ .Get "src" }}" alt="{{ .Get "alt" }}" />
<figcaption>
{{ if eq (.Get "captionType") "markdown"}}
{{ .Get "captionType" | markdownify }}
{{ else }}
<p>{{ .Get "caption" }}</p>
{{ end }}
</figcaption>
</figure>
shortcode-captioned-image.scss
.content {
.captioned-image {
display: inline-block;
margin-left: auto;
margin-right: auto;
img {
object-fit: contain;
display: block;
}
figcaption {
background: var(--alt-bg-color);
padding: .5em;
}
}
}
Usage
{{< captioned-image
src="LaserCats.jpg"
alt="Meme of four kittens pouncing with lasers coming out of their eyes."
caption="Maybe a caption isn't really needed for these meme."
>}}
Result

code shortcode
code.html
{{ if not (.Get "summary") }}
{{ errorf "The %q shortcode requires a 'summary' argument. See %s" .Name .Position }}
{{ end }}
<details>
<summary>{{ .Get "summary" }}</summary>
<div class="code">
{{ .Inner | markdownify }}
</div>
</details>
_shortcode-code.scss
.content {
.code {
overflow-y: auto;
max-height: 60vh;
}
}
Usage
{{< code summary="Expand to see Hello World" >}}
```cpp
#include <iostream>
using namespace std;
int main() {
cout << "Hello, World!";
return 0;
}
```
{{< /code >}}
Result
Expand to see Hello World
#include <iostream>
using namespace std;
int main() {
cout << "Hello, World!";
return 0;
}
br shortcode
I wanted to insert a newline in a markdown table cell but <br>
was getting stripped. This shortcode quickly unblocked me. It probably isn’t the best way to solve this issue, but works for me.
br.html
<br>
Usage
{{ < br >}}
Result
Okay, it’s what you’d expect. Just trust me on this one.
rawhtml shortcode
An escape-hatch for anytime you need to embed raw html into markdown. I strive not to use this as shortcode will be much more maintainable in the long run, but it can be useful for quickly experimenting.
rawhtml.html
{{ .Inner }}
Usage
{{< rawhtml >}}
<a data-pin-do="embedBoard" data-pin-board-width="700" data-pin-scale-height="750" data-pin-scale-width="300" href="https://www.pinterest.com/robizzle01/made-by-rob-meyer/"></a>
<script async defer src="//assets.pinterest.com/js/pinit.js"></script>
{{< /rawhtml >}}