HUGO

Hugo Shortcodes

Hugo shortcodes are useful when you need to output HTML from your markdown that is incompatible with the goldmark or black friday parser. When the markdown parser encounters an HTML element, it skips processing until that element is closed. The div custom shortcode included below is an example of how to workaround this issue.

Custom shortcodes

Video

Here is the output for the Hugo youtube shortcode when only the YouTube Video ID is supplied as a positional parameter.

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
    <iframe src="https://www.youtube.com/embed/SsoOG6ZeyUI" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen="" title="YouTube Video"></iframe>
</div>

For custom style, use a class. Note that the inline style will be removed from the youtube shortcode output when a named parameter like class is used. Additionally, the id parameter is required for the YouTube Video ID when you add a class parameter. e.g.,

{{< youtube id="w7Ft2ymGmfc" class="video-wrapper" >}}

Respective .video-wrapper Sass:

.video-wrapper {
    position: relative;
    padding-bottom: 56.25%;
    height: 0;
    overflow: hidden;

    > iframe {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        border:0;
    }
}

Custom Shortcodes

Here are some custom shortcodes and tips for creating your own Hugo shortcodes.

If your writing documentation for Hugo shortcodes, you will need to escape the shortcode processing. e.g., {{</* code */>}} will be rendered as {{< code >}}.

div.html

This shortcode is for outputting a div element that wraps your markdown. Useful when you want to specify specific styling for the div and its nested or child elements.

{{/*-----------------------------------------------------------

    {{< div someclass >}} markdown {{< / div >}}
    {{< div class="classList" >}} markdown {{< / div >}}
    {{< div style="property:value" >}} markdown {{< / div >}}

    style parameter: since the semicolon declaration separator `;`
    is not permitted, only one declaration allowed

------------------------------------------------------------*/}}
<div
{{- if .IsNamedParams -}}
    {{ if .Get `class` }} class="{{ .Get `class` }}"{{ end }}
    {{ if .Get `style` }} style="{{ .Get `style` }}"{{ end }}
{{- else -}}
    {{ if .Get 0 }} class="{{ .Get 0 }}"{{ end }}
{{- end -}}>
    {{ .Inner | markdownify }}
</div>
picture.html

Picture element

{{/*-----------------------------------------------------------

  {{< picture src="/images/filename" ext="png" alt="" caption="" class="" >}}

------------------------------------------------------------*/}}
{{ if .Get `class`}}
<div class="{{ .Get `class` }}">
{{ end }}
{{ if .Get `caption`}}
<figure>
{{ end }}
  <picture>
    <source media="(min-width: 768px)"
            srcset="{{ .Get `src` }}.{{ .Get `ext` }}">
    <source media="(max-width: 767px)"
            srcset="{{ .Get `src` }}-m.{{ .Get `ext` }}">
    <img src="{{ .Get `src` }}.{{ .Get `ext` }}"
         alt="{{ .Get `alt` }}">
  </picture>
{{ if .Get `caption`}}
  <figcaption>{{ .Get `caption` }}</figcaption>
</figure>
{{ end }}
{{ if .Get `class`}}
</div>
{{ end }}

Tabs

This is a complex shortcode for creating tabbed content. Here is a working example:

Here is my example content for the first tab.
Here is my example content for the second tab.

tab.html

{{/*-----------------------------------------------------
    Depends on {{< tabcontent >}}

    {{< tab set1 tab1 active >}}Tab 1{{< /tab >}}
    {{< tab set1 tab2 >}}Tab 2{{< /tab >}}

-----------------------------------------------------*/}}
<button data-tabset="{{ .Get 0 }}" data-tabcontent="{{ .Get 1 }}" {{ with .Get 2 }}class="active"{{ end }}>{{ .Inner | markdownify }}</button>

tabcontent.html

{{/*----------------------------
    Depends on {{< tab >}} for tab buttons

    {{< tabcontent set1 tab1 >}}

    markdown content

    {{< /tabcontent >}}
    {{< tabcontent set1 tab2 >}}

    markdown content

    {{< /tabcontent >}}
-----------------------------*/}}
<div class="tabcontent" data-tabset="{{ .Get 0 }}" data-tabcontent="{{ .Get 1 }}">{{ .Inner | markdownify }}</div>

The parameter for the data-tabset at position 0, e.g., set1, must be unique for the tabs used on a given markdown page.

The tab and tabcontent shortcodes depend upon the following Sass and JavaScript. Convert the Sass to CSS using SassMeister if you prefer.

$color__background-light: #f8f8f8;
$color__light-gray: #eaeaea;
$color__text-main: #404040;

.tabs {
    display: flex;
    border-bottom: 1px solid $color__light-gray;
    max-width: 100%;
}

.tabs button {
    min-width: 100px;
    color: lighten($color__text-main, 30%);
    background-color: $color__background-light;
    border: 1px solid $color__light-gray;
    border-top-right-radius: 1rem;
    border-top-left-radius: 1rem;
    outline: none;
    cursor: pointer;
    margin-bottom: -1px;
    padding: 14px 16px;
    transition: 0.3s;
}

.tabs button:hover {
    color: inherit;
    background-color: darken($color__background-light, 5%);
}
.tabs button.active {
    color: inherit;
    background-color: white;
    border-bottom: white;
}

.tabcontent {
    display: none;
}
.tabcontent.active {
    display: block;
}

JavaScript for the tab and tabcontent shortcodes:

(function() {

    var tab = document.querySelectorAll('button[data-tabset]');
    if (tab != null) {

        var i, el, tabcontent, tabset, tabSetList, tabContentList;

        var clear = function(nodeList) {
            for (i = 0; i < nodeList.length; i++) {
                nodeList[i].classList.remove('active');
            }
        }

        var onTabClick = function() {
            tabset = event.target.dataset.tabset;
            tabcontent = event.target.dataset.tabcontent;
            tabSetList = document.querySelectorAll('button[data-tabset="'+ tabset +'"]');
            tabContentList = document.querySelectorAll('.tabcontent[data-tabset="'+ tabset +'"]');
            clear(tabSetList);
            event.target.classList.add('active');
            clear(tabContentList);
            el = document.querySelector('.tabcontent[data-tabset="' + tabset + '"].tabcontent[data-tabcontent="' + tabcontent + '"]');
            if (el != null) {
                el.classList.add('active');
            }
        }

        for (i = 0; i < tab.length; i++) {
            tabset = tab[i].dataset.tabset;
            tabcontent = tab[i].dataset.tabcontent;

            // add `tabs` class to parent element
            if (!tab[i].parentElement.classList.contains('tabs')) {
                tab[i].parentElement.classList.add('tabs');
            }

            // show active content
            if (tab[i].classList.contains('active')) {
                el = document.querySelector('.tabcontent[data-tabset="' + tabset + '"].tabcontent[data-tabcontent="' + tabcontent + '"]');
                if (el != null) {
                    el.classList.add('active');
                }
            }
        }

        for (i = 0; i < tab.length; i++) {
            tab[i].addEventListener('click', onTabClick);
        }
    }

})();
comments powered by Disqus