CSS-Only Footnote Tooltips in Hugo
The blog now shows the text of a footnote when you hover over its citation number, so you can read a reference without jumping to the bottom of the page and back.1 It involves one Hugo partial and a few CSS rules.
I’ve been envious of other blog’s footnote popovers that usually come from a JavaScript library such as Bigfoot or littlefoot, which assemble the bubbles in the reader’s browser. But I wanted to avoid using any of that in order to keep the site as simple as possible with only HTML and CSS.
Hugo’s Markdown renderer, Goldmark, turns each [^1] reference into a superscript link and collects the footnote bodies into a list at the end of the article. The partial below takes the rendered page content, finds each footnote’s HTML in that list, and copies it into a hidden <span> placed beside every inline reference to it. CSS then reveals the span on mouse hover as a small popover above the citation. Because the copying happens during the build, the published page is plain HTML.
The layouts/partials/footnote-tooltips.html file:
{{ $orig := . }}
{{ $c := . }}
{{ range findRE `(?s)<li id="fn:[^"]+">.*?</li>` $c }}
{{ $id := index (findRE `fn:[^"]+` .) 0 }}
{{ $num := strings.TrimPrefix "fn:" $id }}
{{/* Inner HTML of the footnote, minus the ↩︎ backref links */}}
{{ $html := replaceRE `(?s)^<li[^>]*>(.*)</li>$` "$1" . }}
{{ $html = replaceRE `(?s)<a href="#fnref[^>]*>.*?</a>` "" $html }}
{{/* Unwrap <p> — block elements can't live inside the article's paragraph */}}
{{ $html = replaceRE `(?s)</p>\s*<p>` "<br><br>" $html }}
{{ $html = replaceRE `</?p>` "" $html }}
{{ $html = strings.TrimSpace $html }}
{{ $needle := printf `<a href="#%s" class="footnote-ref" role="doc-noteref">%s</a>` $id $num }}
{{ $repl := printf `%s<span class="fn-tooltip" aria-hidden="true">%s</span>` $needle $html }}
{{ $c = replace $c $needle $repl }}
{{ end }}
{{ if and (in $c `class="footnote-ref"`) (eq $c $orig) }}
{{ warnf "footnote-tooltips.html: page has footnote refs but no tooltips were injected — Goldmark's footnote markup may have changed" }}
{{ end }}
{{ $c | safeHTML }}
Then, in your single-page template (typically layouts/_default/single.html), replace {{ .Content }} with:
{{ partial "footnote-tooltips.html" .Content }}
Lastly, style the tooltip box with your CSS:
sup:has(> a.footnote-ref) {
position: relative;
}
.fn-tooltip {
display: none;
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
width: clamp(16rem, 90vw, 38rem);
padding: 0.6em 0.8em;
background: #000000;
color: #ffffff;
font-size: 0.9rem;
font-weight: normal;
line-height: 1.2;
white-space: normal;
overflow-wrap: break-word;
text-align: left;
z-index: 10;
}
sup:hover > .fn-tooltip,
a.footnote-ref:focus + .fn-tooltip {
display: block;
}
The tooltip span carries aria-hidden="true" so screen readers skip the duplicated text and announce only the original footnote link, and the :focus rule reveals the tooltip for keyboard users tabbing through the page. The backref arrows that Goldmark appends to each footnote are stripped from the copy, since a “return to text” link makes no sense inside a tooltip. Paragraph tags are unwrapped because the span lives inside the article’s own paragraph, where block elements are invalid HTML.
The one caveat is that the partial matches Goldmark’s exact footnote markup, which is an implementation detail of Hugo rather than a stable API. If a future Hugo release changes that markup, the tooltips silently disappear while the footnotes themselves keep working as ordinary links. The warnf guard at the bottom covers this case. If a page contains footnote references but nothing was injected, the build prints a warning so you find out from your terminal instead of from a reader.
Lynn Kuok, “The New Arteries of Power: Subsea Cables Are This Century’s Hidden Battleground,” Foreign Affairs, January 2, 2026, https://www.foreignaffairs.com/new-arteries-power. Hover over this citation’s number in the text above to see the tooltip in action. ↩︎
⁂