Hugo Tabs shortcode

Hugo Tabs shortcode

For comparisons, and to save space, tabs are a useful UI element. This shortcode allows to easily create tabs in Hugo.

This article was written for Hugo 0.74.

Usage

There are no options, you can define an unlimited amount of tabs. Markdown markup can be used within tabs.

Headings in the tab content will not be part of the table of contents.

Footnotes1 might behave different as expected, their scope is set per tab. Global footnotes cannot be used (e.g. [^example3]), see the Linux example tab.

Example

{{< tabs >}}
{{< tab "MacOS" >}}
### MacOS

This is tab **MacOS** content.

Lorem markdownum insigne. Olympo signis Delphis! Retexi Nereius nova develat
stringit, frustra Saturnius uteroque inter!
{{< /tab >}}

{{< tab "Linux" >}}
## Linux

This is tab[^example2] **Linux** content.[^example3]

{{% hint warning %}}
Example text that *may* contain **markdown** `markup`.[^example]

[^example]: footnote within a hint.
{{% /hint %}}

[^example2]: footnote within a tab
{{< /tab >}}

{{< tab "Windows" >}}
### Windows

This is tab **Windows** content.

Lorem markdownum insigne. Olympo signis Delphis! Retexi Nereius nova develat
stringit, frustra Saturnius uteroque inter!
{{< /tab >}}
{{< /tabs >}}

MacOS

This is tab MacOS content.

Lorem markdownum insigne. Olympo signis Delphis! Retexi Nereius nova develat stringit, frustra Saturnius uteroque inter!

Linux

This is tab1 Linux content.[^example3]

Example text that may contain markdown markup.1


  1. footnote within a hint. ↩︎


  1. footnote within a tab ↩︎

Windows

This is tab Windows content.

Lorem markdownum insigne. Olympo signis Delphis! Retexi Nereius nova develat stringit, frustra Saturnius uteroque inter!

Code

The HTML shortcode, Tabs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{{ if .Inner }}{{ end }}

{{ $id := .Scratch.Get "tabs-id" }}
{{ $group := printf "tabs-%d" $id }}

<div class="md-tabs">
{{- range $index, $tab :=  .Scratch.Get $group -}}
  {{ $individualTabId := printf "%s-%d" $group $index }}
  <input type="radio" class="hidden" name="{{ $group }}" id="{{ $individualTabId }}" {{ if not $index }}checked="checked"{{ end }} />
  <label for="{{ $individualTabId }}">
    {{- $tab.Name -}}
  </label>
  <div class="md-tabs-content markdown-inner">
    {{- .Content | markdownify | safeHTML -}}
  </div>
{{- end -}}
</div>
{{ .Scratch.Delete "tabs-id" }}

Tab shortcode, it will add its name and content to .Parent.Scratch so the Tabs shortcode can render it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{{ if .Parent }}
  {{ $name := .Get 0 }}

  {{- if not (.Parent.Scratch.Get "tabs-id") -}}
  {{ $id :=  index (seq 1000 | shuffle) 0 }}
  {{ .Parent.Scratch.Add "tabs-id" $id }}
  {{- end -}}

  {{ $group := printf "tabs-%d" (.Parent.Scratch.Get "tabs-id") }}

  {{ if not (.Parent.Scratch.Get $group) }}
    {{ .Parent.Scratch.Set $group slice }}
  {{ end }}
    
  {{ .Parent.Scratch.Add $group (dict "Name" $name "Content" .Inner) }}
{{ else }}
  {{ errorf "%q: 'tab' shortcode must be inside 'tabs' shortcode" .Page.Path }}
{{ end}}

The accompanying SCSS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.md-tabs {
  margin-top: 1rem;
  margin-bottom: 1rem;

  border: 1px solid var(--md-bg-color-dark);
  border-radius: $border-radius;

  overflow: hidden;

  display: flex;
  flex-wrap: wrap;

  label {
    display: inline-block;
    padding: 0.5rem 1rem;
    border-bottom: 1px transparent;
    cursor: pointer;
  }

  .md-tabs-content {
    order: 999; //Move content blocks to the end
    width: 100%;
    border-top: 1px solid var(--md-bg-color-light);
    padding: 1rem;
    display: none;
  }

  input[type="radio"]:checked+label {
    border-bottom: 1px solid var(--link-color);
  }

  input[type="radio"]:checked+label+.md-tabs-content {
    display: block;
  }
}

  1. this is the default behavior ↩︎

Noticed an error in this post? Corrections are appreciated.

© Nelis Oostens