Sidenotes for Markdown

@sirajchokshi

Table of Contents

  1. Quick Demo
  2. Remark Plugin (Not Recommended)
  3. With MDX
  4. Limitations

I recently wanted to add sidenotes (also called margin notes) to my blog. This site is built with Gatsby.js Next.js, and the content is written in Markdown. If you’ve searched for a solution to this problem before, you have likely run into Gwern’s post. They cover known sidenote implementations comprehensively, so we don’t need to do our own homework. We do, however, need to figure out a way to make this accessible in Markdown.

The solution I was looking for entailed:

  1. No JavaScript after build-time
  2. Simple syntax for using sidenotes
  3. The resulting markup is somewhat semantic

Quick Demo

On desktop, this text fills the right margin, and on mobile, it simply collapses into a box below the relevant paragraph.Like this! formatting works here too.

I originally wrote this blog using Remark. The common, hacky way to build custom components using Remark is to create a codeblock with the language set to a specifier for your component instead of a real programming language.

Take, for example, if we wanted to add a newsletter button to our page:

# My title
 
This is a button:
 
```newsletter-button
Subscribe to my newsletter!
```

We would then write a Remark plugin that checks all code blocks in the syntax tree where lang is newsletter-button and then swaps the code block’s node out for a custom HTML node. The code block node is commonly overloaded for custom components since it allows for arbitrary content.

This is pretty much exactly what I did when building sidenotes with Remark. If you just want to see the Gatsby plugin itself, all of it can be found here. I no longer recommend this approach. Keep reading for a more idiomatic method of implementing sidenotes.

With MDX

MDX is a markup format that allows you to use JSX within markdown. It blends rich-text and dynamic presentation methods. As a result, we only use forceful syntax when necessary. If you are used to writing React like I am, this leads to a very comfortable writing experience.

Below is an example of a React component (Sidenote) being used in an MDX file.

<Sidenote content={"An interesting tidbit about foo!"}>
  Something about foo.
</Sidenote>

If you need it, the Sidenote component is implemented as follows:

const Sidenote: FC<SidenoteProps> = ({ children, content }) => (
  <div className="sidenote-wrapper">
    {children}
    <aside>{content}</aside>
  </div>
);

Limitations

While this approach is simple, it is also naive. It requires some manual restraint, as overlaying is not dealt with in any way. In other words, if there are many sidenotes in the page gutter, then they will stack on top of one another. My writing style does not lean on sidenotes too hard, so this works out.

A way around this is to provide some API for specifying the offset of the sidenote. Below is a vanilla CSS solution that uses CSS variables to offset the sidenote. Alternatively, you could use a prop in the MDX component to specify the offset using styled-system, Tailwind, or whatever you prefer.

<Sidenote content={"An interesting tidbit about foo!"} offset={3}>
  Something about foo.
</Sidenote>
sidenote.css
/* Map attributes to CSS variables */
 
[data-offset="1"] {
  --offset: 1;
}
 
[data-offset="2"] {
  --offset: 2;
}
 
[data-offset="3"] {
  --offset: 3;
}
 
...
 
/* Use CSS variables to offset the sidenote */
.sidenote-wrapper {
  margin-top: calc(var(--offset) * 1.5rem);
}