New May 5, 2026

Footnotes in Eleventy

More Front-end Bloggers All from Kitty Giraudel View Footnotes in Eleventy on kittygiraudel.com

After having moved from Jekyll to Eleventy, I realized I could extend Liquid in fancy ways to make some things a little easier (or down right possible). In this article, I’d like to share how I built a tiny footnotes plugin with Liquid. If you are not interested in how the sausage is made and just want to use the code, check eleventy-plugin-footnotes for usage instructions.

I have recently blogged about accessible footnotes again and if you haven’t read the article yet, I recommend you do so you fully grasp what comes next. To put things simply, we need 2 things: a way to register a footnote reference within the text, and a way to display the footnotes for a given page at the bottom of a post. Let’s start with the first one.

Registering footnotes

To author a footnote within text content, we use a footnoteref Liquid tag which takes the footnote identifier and the footnote content as arguments (in that order). It looks like this:

Something about {% footnoteref "css-counters" "CSS counters are, in essence,
variables maintained by CSS whose values may be incremented by CSS rules to
track how many times they’re used." %} CSS counters{% endfootnoteref %} that
deserves a footnote explaining what they are.

The Eleventy configuration would be authored like this:

const FOOTNOTE_MAP = [];

config.addPairedShortcode( "footnoteref", function footnoteref(content, id, description) { const key = this.page.inputPath; const footnote = { id, description };

FOOTNOTE_MAP[key] = FOOTNOTE_MAP[key] || {}; FOOTNOTE_MAP[key][id] = footnote;

return </span><span class="token string">&lt;a href="#</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>id<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-note" id="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>id<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-ref" aria-describedby="footnotes-label" role="doc-noteref" class="Footnotes__ref"></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>content<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&lt;/a></span><span class="token template-punctuation string">; }, );

Here is how it works: when rendering the footnoteref Liquid tag, we retrieve the registered footnotes for the current page (if any) from the FOOTNOTE_MAP map. We add the newly registered footnote to it, and we render an anchor link to the footnote.

Rendering footnotes

For that I created a footnotes.liquid partial which I render at the bottom of the post layout (passing it the current page object), like so:

<article>
	{{ content }}
	{% include "components/footnotes.liquid", page: page %}
</article>

Now, we need a way to retrieve the footnotes from the page. That’s actually not too easy in Liquid unfortunately since there is no way to inject a global variable or simply assign a function call to a variable. Liquid’s utilities mostly aim at rendering HTML (as shown above) so it’s not too straightforward to return an array.

I played around a few solutions, and eventually landed with a wacky filter. Basically I expose a footnotes filter which expects the page as argument, and returns the footnotes for that page.

{% assign footnotes = '' | footnotes: page %}

This is pretty ugly. We need a value to be able to apply a filter, even though that value can be anything since the filter will just replace it with an array of footnotes.

Here is how it’s defined:

config.addFilter(
	"footnotes",
	// The first argument is the value the filter is applied to,
	// which is irrelevant here.
	(_, page) => Object.values(FOOTNOTES_MAP[page.inputPath] || {}),
);

From there, we can render the necessary markup to output the footnotes using a for loop to iterate over each of them.

{% assign footnotes = '' | footnotes: page %}
{% assign count = footnotes | size %}
{% if count > 0 %}
<footer role="doc-endnotes">
	<h2 id="footnotes-label">Footnotes</h2>
	<ol>
		{% for footnote in footnotes %}
		<li id="{{ footnote.id }}-note">
			{{ footnote.description }}
			<a
				href="#{{ footnote.id }}-ref"
				aria-label="Back to reference {{ forloop.index }}"
				role="doc-backlink"
				>↩</a
			>
		</li>
		{% endfor %}
	</ol>
</footer>
{% endif %}

Wrapping up

So to sum up:

That’s about it. Pretty cool, huh? ✨

If you are interested in using these footnotes in Eleventy, check out eleventy-plugin-footnotes on GitHub. There are install instructions, guidelines and examples.

Scroll to top