alvrod / blog

CSS-only responsive sidenotes in Hugo

Adding simple sidenotes to my blog in CSS

For this blog I am using Hugo ʕ•ᴥ•ʔ Bear Blog; I like it because of its minimalist, clean, responsive design. When writing my recent post about emails, I wanted to add sidenotes (or maybe more accurately, margin notes) to it for side comments, so obviously I spent more time adding support for sidenotes than actually writing the article itself1.

I want to mention two articles that were inspirational, although in the end I have a simpler implementation that I will describe in this post:

The implementation keeps the responsiveness and is Javascript free by using CSS Flexbox.

Usage as a Hugo shortcode2

I created a sidenote Hugo shortcode that can be used like so:

When rendered, it looks like this:


This a regular part of the main body of the article. It can have newlines and even Markdown which will be rendered normally.

Within the implementation of the shortcode, this content can be referred to as the first parameter with .Get 0.

This is the content of the sidenote. It can also have newlines and will render Markdown normally.

Within the implementation of the shortcode, this content can be referred to as the “inner” parameter with .Inner.


So, it will add the note as a box to the right of the first content. And the only limitation (which you can probably notice even in this example) is that if the sidenote content is longer than the actual content, then extra vertical whitespace is added (which I am fine with, and can be avoided by adding enough content that this is no longer the case).

Implementation

Hugo expects your shortcodes to be defined in a HTML file with the following path pattern:

layouts/shortcodes/<shortcode>.html

so, in this case,

layouts/shortcodes/sidenote.html

And this is the full content of this file:

<div class="paragraph-with-sidenote">
    <div class="sidenote-paragraph">
    {{ .Get 0 | markdownify }}
    </div>
    <div class="sidenote-box">
        <div class="sidenote-note">
        {{ .Inner | markdownify }}
        </div>
    </div>
</div>

We organize the content in a container div, which has

Of course, all of the Flexbox stuff is in a separate CSS file, which is setting the content as a flexbox which can extend further right than the regular blog content up until the edge of the window. Then, we calculate the size of the sidenote box so that it fits nicely in the remaining space between the content width and the edge of the window, but if the available space is too low, then we make it go as an additional row below.
This structure is a nice example of my golden rule of frontend development: when in doubt, add divs.

In order to have this additional CSS available in my pages, I added a layouts/partials/custom_head.html file including it like this:

{{ range .Site.Params.customCss -}}
    <link rel="stylesheet" href="{{ . | absURL }}">
{{- end }}

And then I can add this in the Params of the config.toml:

customCss = ["css/sidenote.css"]

And this is the full content of that file:

.paragraph-with-sidenote {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: flex-start;

    /* allow this box to go beyond the normal width to the right 
       so that there is space for the sidenote 
    */
    width: calc(720px + (100vw - 720px)/2.1);
}

.sidenote-paragraph {
    padding-right: 1rem;
    width: 720px;
}

.sidenote-box {
    width: calc((100vw - 720px)/3);
}

.sidenote-note {
    background-color: whitesmoke;
    border: solid 1px grey;
    border-radius: 10px;
    box-sizing: content-box;
    padding: 0.8rem;
}

/* below 1000 there isn't enough space for a sidenote on the right margin anyway. 
   force widths so that it goes on a block below the paragraph. 
*/
@media screen and (max-width: 1000px) {
    .paragraph-with-sidenote {
        width: 100%;
    }

    .sidenote-box {
        width: 100%;
    }
}

For this design I do rely heavily on the fact that the blog content width is fixed to 720px.

About the width calculations:

width: calc(720px + (100vw - 720px)/2.1);

The window has a 100vw width, out of which we need to reserve 720px for the blog content, so the space available for the sidenote is half of that (the other half is the left margin). For the flexbox, we need it to have width equal to blog content + sidenote content, hence the formula. However I was seeing a small horizontal scrollbar which shouldn’t be there (maybe a padding somewhere?) so to avoid that I reduced it a bit further by dividing between 2.1.

As for the width of the sidenote:

width: calc((100vw - 720px)/3);

I found that it doesn’t look nice if the sidenote stretches to the very edge of the window, and wanted to cut it a bit shorter, therefore /3 instead of /2.


  1. Well, actually, my wife helped me a lot to make it work, since I don’t actually know anything about HTML or CSS. ↩︎

  2. See shortcodes and Create Your Own Shortcodes↩︎