Semantic sidenotes for the web

I will try to keep it short, because I have a tendency to make stories longer than necessary. My relatives already mocked my rambling when I was a teenager. Complained that my stories had no begin and no end.

I noticed that in my writing too. It’s good practice to make blog posts short. But I like to add details, anecdotes and silliness.

Luckily, there’s an old typographical solution for that: sidenotes. I can put all the info that’s not critical for the main story there, so you, dear reader, can just skip that kind of frivolry, if you’re not interested.

Handwritten page with wide margins containing notes
This late-13th century page has wide margins for notes. Although the notes may have been added later in this example, it’s not uncommon for printed texts to have sidenotes from the author.

On this website, sidenotes appear next to the article when there’s enough space. On small screens, they’re hidden by default and can be made visible with a tap. Here’s (sidenote: Actually, here’s the example! This is the actual sidenote. ) .

The problem is, it’s hard to find a properly semantic HTML solution to do this.

Requirements

My sidenotes have two parts:

  1. The sidenote’s content
  2. A word or a span of words that it refers to.

My additional requirements for them are:

  • The span that the sidenote refers to should have a proper semantic HTML element (no abuse of elements made for other purposes)
  • The content of the sidenote should have a similarly valid HTML tag. Additionally:
    • The sidenote content may contain span elements such as <a> and <em>.
    • The sidenote content may contain clickable elements that can receive keyboard focus.
    • The sidenote content must be stylable.
  • The elements should not cause auto-closing of their parent <p> tag.
  • Reader modes and apps like Pocket and RSS readers should show sidenote content in a typographically acceptable way. That means at the very least that I can’t rely on website’s CSS to place and style the elements correctly.
  • The sidenote content should be read by screen readers, in a flow that makes sense. That likely means the two parts should be be placed together.

Elements that don’t work

For sidenote content

Here are some options that I considered, with help from answers on my StackOverflow question.

Let’s first get footnotes1 out of the way. Footnotes are not the same as sidenotes. They don’t appear in the margin, but below the text. That requires one click to go there and another to go back to the article. Way too much effort. Moreover, most of my sidenotes make no sense without the context of the sentence they refer to. That also rules out the <aside> HTML element.

Asides are meant for, quoting MDN web docs: “a portion of a document whose content is only indirectly related to the document’s main content”.

The definition element <dfn> could be abused for sidenotes, but my sidenotes are usually not about definitions of words. The <details> element comes close to what I need; it has two parts, like my sidenotes:

<details>
  <summary>Details</summary>
  Here would be my sidenote content
</details>

Unfortunately, it’s a block element. That means that it shouldn’t be placed inside paragraph. Here’s what happens

<summary>when</summary>Sorry, this is hard to follow with the line breaks.
you do that. Browsers automatically add a close tag for that paragraph. As you can see, this causes the words following the sidenote to appear in a new paragraph. And Jekyll doesn’t handle it well, but there’s probably a workaround for that.

Finally there’s the abbr element. It’s even closer to a solution, because it can be placed inline. Unfortunately, it’s meant for abbreviations only. Worse, the meaning (or title as it’s called) can only appear on mouse hover and can’t be styled. It can’t contain markup like links either. Example: UX/UI design.

For the phrase that gets the asterisk

I need an HTML tag for the span of words to which the sidenote refers: the phrase with the asterisk in the main text. An simple <a> is commonly used for footnotes, but because I’m not sending the user to another place in the document, that feels off.

On small screens the phrase is clickable and toggles the visibility of the sidenote. The <button> tag would be perfect for it, but buttons are hidden by Safari and Firefox’s reader modes, leaving a gap in the sentence.

In an earlier version of this post, I wrote I use a <span>, but a reader’s comment had something better!

My solution for semantic sidenotes

My solution for now is a combination of HTML, CSS and JavaScript. The HTML looks like this:

<!--
I added line breaks for code readability.
In reality, there should be no line breaks before the <span>s.
-->
<p Some sentence with a<span class="sidenote sidenote--collapsed">
  <span class="sidenote sidenote--collapsed">
    <label
      class="sidenote__button"
      tabindex="0"
      title="Here's the content of the sidenote."
      aria-describedby="sidenote-1"
      onKeypress="handleSidenoteKeypress(event)"
      onClick="handleSidenoteButtonClick(event)">
      label
    </label>
    <small
      id="sidenote-1"
      class="sidenote__content" >
      <span class="sidenote__content-parenthesis">
        (sidenote:
      </span>
      Here's the content of the sidenote
      <span class="sidenote__content-parenthesis">
        )
      </span>
    </small>
  </span>
for the sidenote.</p>

I wrap the whole thing in a span, (sidenote: As a nice side effect, on large screens, where the sidenote appears next to the body text, both asterisks get hover styling when one either the content or the inline span has the mouse cursor over it. ) to keep the required JavaScript simple.

Then there’s the inline phrase that gets the asterisk, or let’s call it a label. I’m using the <label> tag after all. That idea came from Roman Komarov who uses it for this purpose too. I used to think a <label> must refer to an <input>, but that’s actually seems to be not true. With tabindex="0" and some CSS, the thing behaves just like a button. It has a title attribute, so the content is also shown on mouse hover.

For the sidenote content I use the <small> element. MDN says it was made for representing side comments and small print. So semantically that’s pretty good. It has an additional benefit: by default it should be rendered with a smaller font size. So even when CSS is not applied, like in my RSS reader, it should appear smaller.

The spans with the class sidenote__content-parenthesis are hidden with CSS. They’re mainly there for screen readers. I found that when my iPhone reads my posts out loud, only adding parenthesis around the sidenotes is confusing. By adding the phrase ‘sidenote: ‘, it’s much clearer why the sentence is interrupted.

The JavaScript I added is straightforward; it just toggles classes on the wrapper span .sidenote:

function handleSidenoteButtonClick(e){
  const el=e.target;
  el.parentNode.classList.toggle('sidenote--expanded');
  el.parentNode.classList.toggle('sidenote--collapsed');
  el.blur();
}

function handleSidenoteKeypress(e){
  if(e.key === 'Enter' || e.key === ' '){
    const el=e.target;
    e.preventDefault();
    el.parentNode.classList.toggle('sidenote--expanded');
    el.parentNode.classList.toggle('sidenote--collapsed');
  };
}

There’s a whole bunch of CSS to make the content appear in the right place, hide it by default on small screens and to add the asterisks. I’m not going to include all of it—use the inspector of you’re interested. But it’s worth mentioning that just hiding the sidenote’s content with display: none causes it to be ignored by screen readers. So instead, I hide it like this:

.sidenote--collapsed .sidenote__content,
.sidenote__content-parenthesis{
  position: absolute;
  left: -99999px;
  top: auto;
}

Is this solution good enough?

The solution above looks good in the major browsers; when CSS is applied things look as intended. When CSS is not available, default styles make the everything look as intended. In that scenario, the content in the example code above would look like this: “(sidenote: Here’s the content of the sidenote)”. No weird line breaks there.

The only screen reader tests I did were with Safari on iOS and macOS. It speaks the text just as intended, including the ‘sidenote:’ part of it (which is the reason I included that part, otherwise the sentences sound weird).

The reader modes of Firefox and Desktop Safari also do a fine job. They correctly render the content within the <small> tags smaller. Same for Pocket and the RSS reader I use, Reeder. In my previous version (with nested spans) Pocket hid the whole .sidenote component. But now that I use <label>, it’s all good. Chrome’s (sidenote: The experiment ended shortly after I published this post and Chrome no longer has a reader mode at all. ) reader mode adds some superfluous linebreaks, but doesn’t remove any content. Mobile Safari’s reader view hides the sidenote content, but the label remains, so there’s still a readable main article.

There are some issues remaining with this solution though:

  • If you do an in-page search (Ctrl+F/CMD+F in your browser) for a something that’s part of the sidenote content when the sidenote content is hidden (because of a narrow viewport), the browser scrolls to something that’s not visible. Like, hey here’s what you’re looking for, but I’m not showing it to you.
  • There’s a lot of HTML.
  • It requires CSS and JavaScript.

Conclusion

Carefully consider your requirements

My requirements are very specific. If you’re looking for a (sidenote: Have a look at the Tufte CSS project and especially this discussion about it. ) for placing sidenotes, consider a simple anchor link to an aside or a footnote. Note that with such a solution:

  • The sidenote content should be understandable without the main content.
  • The sidenote cannot be placed inside a <p> tag, as the browser will autoclose it there.
  • You need JavaScript to vertically align the sidenote with the phrase it refers to.
  • On narrow viewports there’s little practical difference between scroll jumping down to footnote and to a sidenote below the paragraph.

What’s next?

Arguably, I (and you) would be better off, if I tried to write more concisely. But beyond this blog, I think there’s a valid case to be made for sidenotes. They’ve been around in print media for centuries. A better separation in the representation of main content and details can be useful in many areas. I think especially long form educational and journalistic content could benefit a lot from sidenotes.

I don’t think my solution for sidenotes is great just yet. I submitted the problem to the Web We Want initiative, where it got a Judge’s Pick and got attention from people at the major browser vendors. So maybe, sometime, there will be a native HTML solution!

Updates

Based on reader’s reactions I’ve made some tweaks in the text above. In August 2020 Gwern published an overview of sidenote implementations. It contains a thorough analysis of challenges and solutions. There’s table comparing all solutions they researched, including this one.

In October 2020 I posted Making semantic sidenotes without JavaScript with an improved version of what I presented in this post.

  1. Here’s the example footnote. It’s useless without context. It may have made sense in print times, but on a website, I don’t see much of a benefit over sidenotes.