Skip to main content

Static semantic sidenotes

25 Feb 2021

The best online equivalent of footnotes are adaptive sidenotes. Instead of hiding at the bottom of the page — and making the reader scroll to the end to see them — these notes sit in one margin on wide screens, and snap in to the main text column on narrow ones. 1 1 This is an example. On a desktop browser, you can widen and narrow your window to see it snap in.

Most sidenote implementations use JavaScript. (This is fine; most websites use JavaScript.)

But most sidenote implementations use it for a kind of weird reason. They don’t need it for getting the positioning right, or for making the adaptive behavior work. They only need it to sneak around HTML’s block/inline distinction.

What if instead we embrace that distinction? And maybe even treat it as valuable? This is what I’ve been doing lately on this site. The payoff is a static site (this is fine; some websites are static), Markdown that’s easy to read and write, and nicely semantic adaptive behavior.

Fighting the HTML

Some notes 2 2 like this one are simple in structure. Others are more complex.3

For instance, this one contains several paragraphs of text. This paragraph goes on and on for a bit to make the point.

It also includes a code snippet.

puts 'Hello, world.'

There, wasn’t that nice?

In print, both kinds can occur anywhere, including in the middle of a sentence.

But HTML has other ideas.


Suppose we want an HTML element to represent a note. Should it be an inline element like span or a block-level one like div?

Using an inline element lets us put our notes anywhere: mid-paragraph, mid-sentence, wherever we want.

<p>
  This is a note
  <span class="note">
    in the form of a span
  </span>
  within a paragraph.
</p>

But it prevents us from putting block-level things (like whole paragraphs, divs, or tables) inside them.

<span class="note">
  <p>
    You can't put a paragraph inside a span.
  </p>
  <p>
    It's malformed HTML!
  </p>
</span>

Using a block-level element lets us put anything we want inside our notes.

<p>
  This is a paragraph with a note outside it.
</p>
<div class="note">
  <p>
    This time the note is a div.
  </p>
  <p>
    It contains several paragraphs of its own.
  </p>
</div>

But they can’t appear inside a paragraph, only outside.

<p>
  Putting a div inside a paragraph
  <div class="note">
    Regardless of what's in the div!
  </div>
  is malformed HTML.
</p>

There are a lot of fancy ways of getting around the dichotomy. In most of them, you define the note someplace other than where you use it. Maybe you write it in a div at the end of the document (so it can contain anything), and then you use JavaScript to position it where you want it (so it can appear mid-paragraph).4

Most of the implementations in Gwern’s recent overview use JavaScript in this or a similar way. A few are static, and those essentially force you into one branch of the dichotomy or the other: in one, notes are always spans with all of their limitations, and in a few others, they are always asides or divs with all of theirs.

It’s interesting, though, that this fairly basic thing requires fighting the structure of HTML in the first place.

Letting the HTML win

But there’s another alternative, which is to accept the dichotomy — to let the HTML win instead of fighting it. On this approach, there are just two fundamentally different kinds of sidenote:

div sidenotes
Can contain any HTML
Can’t occur within paragraphs
span sidenotes
Can’t contain block-level HTML
Can occur within paragraphs

This might sound weird. But I think that’s mostly because footnotes evolved in print, where the block-versus-inline distinction isn’t important. Information types that evolved on the web are almost always specified as block or inline, and this tends to feel perfectly natural.5

And consider: suppose a publication you were writing for had an editorial guideline that said ‘Don’t place a complex footnote in the middle of a paragraph — it disrupts the flow of the text and the reader’s concentration.’ You might very well think that was good advice. Well, that advice amounts to ‘Don’t put a div sidenote mid-paragraph.’

Anecdotally, I don’t really miss long mid-paragraph notes now that I’ve been avoiding them for a little while.


One advantage of this approach is that you can write well-formed HTML, and can write it in logical order, with the notes in their natural context.

<p>
  Some notes
  <span class="note">like this one</span>
  are simple in structure.
</p>
<p>
  Others are more complex.
</p>
<div class="note">
  <p>
    For instance, this one contains several 
    paragraphs of text.
  </p>
  <p>
    It also includes...
  </p>
</div>

Or, if you write in a markup language like Markdown and insert your notes as includes, it means your notes won’t trigger spurious paragraph breaks, because the ones that interrupt a p element can only occur between paragraphs anyway. This was, in fact, my original motivation for doing things this way.

Another advantage is that placing the notes on the page is very, very simple. Each note element appears at the height where it should be rendered, and only needs a negative margin to put it outside the text column.

span.note, div.note { 
  position: relative;
  width: 200px;
  margin-right: -250px;
}

If notes might clash, float and clear can prevent it.

span.note, div.note { 
  float: right;
  clear: both;
}

And since notes can be snapped into the main text column, it’s nice to distinguish them from body text using color, size, or weight as well as position — for instance:

span.note, div.note {
  color: #555;
  font-weight: 300;
}

Semantic behavior

Yet another advantage — and this was what took this from “fun party trick” to “good idea” for me —  is that the notes can snap inline in semantically appropriate ways. span sidenotes, which represent short mid-paragraph asides, can be flowed in with the rest of their paragraph, distinguished now only by their lighter color, lighter weight, smaller size, or whatever other distinguishing feature you’ve given them.

@media print, screen and (max-width: 1200px) {
  span.note {
    display: inline;
    float: none;
    text-indent: 0;
    margin: 0;
  }
}

div sidenotes, representing whole sections of text that sit between paragraphs, can be displayed like other sections or paragraphs.

@media print, screen and (max-width: 1200px) {
  div.note {
    display: block;
    width: inherit;
    float: none;
    margin: 1.5em 0;
  }
}

They could also be displayed like block quotes, with a smaller font size, larger margins, or both — another traditional way of marking a lengthy passage as separate that would be inappropriate for a short one.

This is the biggest reason I use this style of sidenote on my own site. I like that mobile readers get to see my asides in ways that look reasonable and appropriate: sometimes as an addition or continuation to a paragraph that’s already underway, other times as an independent paragraph or more.

Implementation

If you were going to do this, you’d want a few extra odds and ends. I number my sidenotes, and some kind of sidenote mark * * like this one is essential for span sidenotes — without a mark, you wouldn’t even know where in the sentence the note was supposed to go.

The full code I use for a span sidenote looks like this, with links between the note marker and the note.

<p>
  Some notes<sup class="sidenote-widget">
    <a href="#short" id="shortbackref">
      2
    </a>
  </sup>
  <span class="note">
    <a id="short"></a>
    <sup class="sidenote-widget">
      2
    </sup>like this one
    <a href="#shortbackref" class="sidenote-widget"></a>
  </span>
  are simple in structure...
</p>

The code for div notes is similar, though it requires putting a note marker in the preceding paragraph.

<p>
  Others are more complex.<sup class="sidenote-widget">
    <a href="#usual" id="usualbackref">
      3
    </a>
  </sup>
</p>
<div class="note">
  <p>
    <a id="usual"></a>
    <sup class="sidenote-widget">
      3
    </sup>For instance, this one 
    contains several paragraphs of text.
  <p>
  <p>
    It also contains...
  </p>
</div>

I use Jekyll, and both of these live in my _includes directory so I can drop them into a Markdown file quickly.

When snapping notes back into the main text column, I hide these various markers — and for span notes I also make sure they have a space before them.

@media print, screen and (max-width: 1200px) {
  .sidenote-widget { 
    display: none; 
  }
  span.note::before {
    content: " ";
  }
}

For wider viewports that let the note sit in the margin, consistent vertical spacing also requires a small adjustment. Because they’re block elements, div notes end up one line-height lower than span notes. And if the first paragraph in a container has a top margin, the first paragraph of a div note is lowered by that much too. Assuming line-height is 1.5, undoing that extra space looks like this.

div.note { margin-top: -1.5em; } 
div.note p:first-child { margin-top: 0; }