Using Tippy with Hugo

Tippy (Tippy.js - Tooltip, Popover, Dropdown, and Menu Library) is a “tooltip, popover, dropdown, and menu solution for the web, powered by Popper. It provides the logic and optional styling of elements that “pop out” from the flow of the document and float next to a target element.

In case anyone is interested in using it with Hugo, here’s some code snippets I wrote which allow content in one part of the part to display in a popup in another part.

Defining a Definition

I created a shortcode what I could use in my MD files that I use it like: {{% defdef "green" %}}When you mix blue and yellow{{% /defdef %}}

The first arguments is the term, the optional second argument is the text to display. If not specified, the term is displayed. The inner is the definition of the term.

{{ $term := .Get 0 }}{{ $md5 := md5 ( $term ) }}<span class="defdef"><a id="def_{{ $md5 }}">{{ if len .Params | eq 2 }}{{ .Get 1 }}{{ else }}{{ $term }}{{ end }}</a><a href='#def_{{ $md5 }}'><i class='fa-solid fa-2xs fa-link'></i></a></span>&nbsp;{{ .Inner }}{{ $content := printf "%s" "</p></div>" | printf "%s%s" ( .Inner | markdownify ) | printf "%s%s" "...<br/>" | printf "%s%s" ( $term ) | printf "%s%s" "' style='display: none;'><p>" | printf "%s%s" $md5 | printf "%s%s" "<div id='content_" | printf "%s\n" }}{{ .Page.Scratch.Add "popupContent" ( slice $content ) }}

This is one long line so no newlines appear in the HTML. Here’s the breakdown:

  1. Use a MD5 to create an anchor: {{ $term := .Get 0 }}{{ $md5 := md5 ( $term ) }}
  2. Display the term: <span class="defdef"><a id="def_{{ $md5 }}">{{ if len .Params | eq 2 }}{{ .Get 1 }}{{ else }}{{ $term }}{{ end }}</a><a href='#def_{{ $md5 }}'>
  3. Display link to here (uses fa - FontAwesome characters): <a href='#def_{{ $md5 }}'><i class='fa-solid fa-2xs fa-link'></i></a>
  4. Display the definition itself:  {{ .Inner }}
  5. Define what should popup (this is horribly complex, but necessary so strings can be appended. It’s all backwards!): {{ $content := printf "%s" "</p></div>" | printf "%s%s" ( .Inner | markdownify ) | printf "%s%s" "...<br/>" | printf "%s%s" ( $term ) | printf "%s%s" "' style='display: none;'><p>" | printf "%s%s" $md5 | printf "%s%s" "<div id='content_" | printf "%s\n" }}
  6. Store the definition for use later: {{ .Page.Scratch.Add "popupContent" ( slice $content ) }}

Defintion Reference

This shortcode refers to the definition, and displays the popup, like: {{% defref "green" %}}

{{ $md5Def := md5 ( .Get 0 ) }}{{ $md5Ref := md5 ( now.UnixNano ) }}<span class="defref"><a id='ref_{{ $md5Ref }}' href='#def_{{ $md5Def }}'>{{ if ( eq ( len .Params ) 2 ) }}{{ .Get 1 }}{{ else }}{{ .Get 0 }}{{ end }}</a></span>{{ $content := printf "%s" "').innerHTML, allowHTML: true, arrow: true, duration: [1000, 1000], interactive: true, maxWidth: 400, placement: 'auto',});</script>" | printf "%s%s" $md5Def | printf "%s%s" "', { content: document.getElementById('content_" | printf "%s%s" $md5Ref | printf "%s%s" "<script>tippy('#ref_" | printf "%s\n" }}{{ .Page.Scratch.Add "popupEnablement" ( slice $content ) }}

Here’s the breakdown:

Get the MD5 for the definition term. Used to link to, a1. nd therefore must match with the definition: {{ $md5Def := md5 ( .Get 0 ) }}
2. Get an MD5 for this reference. Used as the identifier for this element: {{ $md5Ref := md5 ( now.UnixNano ) }}
3. Display the reference: <span class="defref"><a id='ref_{{ $md5Ref }}' href='#def_{{ $md5Def }}'>{{ if ( eq ( len .Params ) 2 ) }}{{ .Get 1 }}{{ else }}{{ .Get 0 }}{{ end }}</a></span>
4. Create JavaScript which must run later: {{ $content := printf "%s" "').innerHTML, allowHTML: true, arrow: true, duration: [1000, 1000], interactive: true, maxWidth: 400, placement: 'auto',});</script>" | printf "%s%s" $md5Def | printf "%s%s" "', { content: document.getElementById('content_" | printf "%s%s" $md5Ref | printf "%s%s" "<script>tippy('#ref_" | printf "%s\n" }}
5. Store the JavaScript for use later: {{ .Page.Scratch.Add "popupEnablement" ( slice $content ) }}

Final bits

The problem building this was that the JavaScript libraries needed to be loaded and the content needed to be available before the JavaScript which enabled everything was executed. This is why the content and JavaScript are attached to the Scratch of the page.

In the _default/baseof.html, right at the bottom but before the </body>, load in the Tippy code:

{{ if eq hugo.Environment "development" }}
{{ "<!-- Popper.js Development -->" | safeHTML }}
<script src="https://unpkg.com/@popperjs/core@2/dist/umd/popper.min.js"></script>
<script src="https://unpkg.com/tippy.js@6/dist/tippy-bundle.umd.js"></script>
{{ end }}

{{ if eq hugo.Environment "production" }}
{{ "<!-- Popper.js Production -->" | safeHTML }}
<script src="https://unpkg.com/@popperjs/core@2"></script>
<script src="https://unpkg.com/tippy.js@6"></script>
{{ end }}

Then, include the definition (popup) content:

{{ range .Page.Scratch.Get "popupContent" }}
{{ . | safeHTML }}
{{ end }}

Then, include the JavaScript (popup enablement):

{{ range .Page.Scratch.Get "popupEnablement" }}
{{ . | safeHTML }}
{{ end }}

I hope somebody finds this useful!

5 Likes