footnotes on the web
Note This article uses the very technique described.
Feel free to Inspect
along!
I recently found a nifty
:target
trick by CSS-Tricks
and decided to play around with the technique. Googling for other footnote techniques,
I stumbled upon a Sitepoint article
that uses
CSS counters.
This article is about putting those two techniques together (with a bit of my own flair ontop).
1. The :target
trick
The gist of it is to use regular anchor links (<a>
) and the browser’s ability to scroll
to a matching fragment ID.
We can then highlight the clicked footnote via the
:target
CSS pseudo-class.
This pseudo-class selector matches an element with an id
matching the URL’s fragment.
Imagine that your site has an /#about-section
anchor. Once a user has clicked on a link
to take them to that anchor, the :target
selector’s styles will be applied.
Demo 👇
Some link to an imaginary Section 42.
Notice how it will be styled via the :target
pseudo-class once clicked.
<a href="#target-styles-demo">Section 42</a>
<!-- content... -->
<p id="target-styles-demo">[Section 42]</p>
#target-styles-demo:target {
background-color: cornflowerblue;
color: white;
}
[Section 42]
We can then leverage this “active fragment” styling to highlight the relevant footnote.
2. The CSS counter
I really like this one, as I rarely see a great use-case for CSS counters outside lists and I always find them super nifty! I find that counters make perfect sense for footnotes and they almost seem made to fit for this very purpose.
We are assigning an aria-describedby
attribute to our footnote references
and then assigning our CSS counter to that. Free accessibility bonus!
We then display the counter value in a pseudo-element, since a pseudo-element’s
content
property can access the counter and display its value.
The attribute isn’t mandatory here. If you wish to save on characters, you could also apply the counter by any class selector.
/* Let's create a counter on a wrapper element */
article {
counter-reset: footnotes;
}
/* Here we increment the counter for every footnote reference */
a[aria-describedby='footnote-label'] {
counter-increment: footnotes;
text-decoration: none;
color: inherit;
outline: none;
}
/**
* Actual numbered references
* 1. Display the current state of the counter (e.g. `[1]`)
* 2. Style text as superscript
*/
a[aria-describedby='footnote-label']::after {
content: '[' counter(footnotes) ']';
vertical-align: super;
font-size: 0.5em;
margin-left: 2px;
color: blue;
text-decoration: underline;
cursor: pointer;
}
a[aria-describedby='footnote-label']:focus::after {
outline: thin dotted;
outline-offset: 2px;
}
Here’s the HTML for the footnote references we sprinkle into our content
(we give it an id
so we could link back to it from the footnotes - providing
the user a way to jump back right where they left off)
<a aria-describedby="footnote-label" id="cite-ref-1" href="#cite-1">Section 42</a>
3. Indicating active footnote
With almost all the pieces in place, we can now add a touch of magic so that the user would know what footnote they were taken to (and vice-versa when throwing them back into content).
If you haven’t already experimented, click on this footnote to see the effect.
/* Inline footnotes */
a[aria-describedby='footnote-label']:target {
animation: highlight 3s;
}
/* Wrapper of your footnotes */
footer :target {
animation: highlight 2.75s;
}
@keyframes highlight {
from {
outline: 10px solid cornflowerblue;
}
to {
outline: 10px solid transparent;
}
}
Bonus:
I’m including a “back to top” link with every footnote in our footer.
For this to work, we’ll also add an ID to the link in the content that
references a footnote. I’m using a [cite-1
, cite-ref-1
] convention
here because at first I had long descriptive names per footnote, but
it got cumbersome to
- type them out each time
- try and avoid clashes
- remember what I had just typed when creating the “Back to top” button in the footer
I saw that Wikipedia uses the same for their footnote fragments (without referencing back to the content)! And this will work just as well for GitHub’s Markdown preview.
Laa-dee-daa and a <a href="#cite-1" id="cite-ref-1">scientific term</a>.
<!-- ...content -->
<footer>
<p id="cite-1">Explanation of fancy term. <a href="#cite-ref-1">☝️ Back</a></p>
</footer>
Issues
Turns out SPAs have a hard time with this, since the HTML5 History API’s
pushState()
method does not activate the :target
selector’s styles.
There is also a reported issue on Chromium and the behaviour seems consistent across browsers.
Remedies:
- Changing the URL the old-fashioned way (
window.location.href = 'page#some-hash'
) will trigger the styles - For Vue and React link components (e.g.
<router-link>
), you could add a click listener
<router-link
to='/page#some-hash'
@click.native="() => window.location.hash = '#some-hash'"/>
If you’re using Saber as I am, you can add the
saber-ignore
property to your links.
Practicality
So it is cumbersome to write these in a Markdown file, as you’ll need to whip them up in vanilla HTML every time. You might be able to get away with writing a Vue/React component for your Static Site Generator, or maybe a Markdown plugin to handle these.
After all, one of Markdown’s hyperlink syntaxes is itself a reference mechanism:
[Link test][1]
[1]: https://en.wikipedia.org/wiki/Weissman_score
So maybe that could be cleverly transformed to Footnotes when the reference is not a valid link? 🤔 Maybe we’ll explore that in the future together.
Even with the troublesome DX, I like the academic feel of properly referencing your statements.
If you’d wish to check out the demo code, this whole article is just a single Vue component! Browse the source