Table of Contents
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:
- No JavaScript after build-time
- Simple syntax for using sidenotes
- 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.
Remark Plugin (Not Recommended)
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:
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.
If you need it, the Sidenote component is implemented as follows:
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.