Emacs, scripting and anything text oriented.

Sidenotes using only CSS

Kaushal Modi

Through the Mastodon-verse, one day I somehow landed on the amazing website https://takeonrules.com/ by Jeremy Friesen and I got motivated to learn about sidenotes once again.

This post describes my fresh attempt at getting the sidenotes to work.

This is a post in the “Sidenotes” series.

2022-02-05Sidenotes using ox-hugo
2022-02-03Sidenotes using only CSS

I had dabbled into sidenotes CSS about three years back when I came across this blog post by Frans de Jonge, but that attempt just stayed in a git branch and never panned out. This time, I was more determined  You need that kind of determination when you are working with CSS 😄 and I got to work by looking online for resources on “sidenotes using CSS”, and my first stop was Kenneth Friedman’s blog post on Marginal Notes.

I liked how the author introduced the basic steps for implementing marginal notes—that you need to (i) restrict the width of the body text so that you can fix the sidenotes, (ii) put the notes in a special tag like <aside>, and (iii) add CSS to push the <aside> tag content outside of the body text.

I really liked it because point (i) was already implemented on this website.

I implemented the later two points using the examples on that blog post, examples from few more blog posts and then a bit more of fiddling with CSS on my own.

HTML elements for sidenotes #

Regarding point (ii) above, I later realized that using the <aside> tag was a wrong choice for sidenotes. Hugo (or rather its Markdown parser Goldmark) auto-wraps these tags in <p> and so we get forced paragraph breaks around them.

That problem did not show up immediately. But once I started to get the CSS sidenote counters  sidenote counters allow you to easily find the sidenotes corresponding to the those counter numbers referenced in the body text. This is really helpful when you have a lot of sidenotes bunched together. to work, I saw that the reference counter numbers in the main text would always jump to the next paragraph! After few minutes .. OK .. after a lot more minutes, after reviewing the generated HTML, and comparing with the CSS seen on various sites, I realized that the <aside> tag was the culprit!

Later, I find this another blog post which confirmed what I concluded above:

Markdown rendering generates <p> tags. According the to spec, <p> tags cannot have a block element inside them. When you try to put a block element, such as <div> inside <p>, the browser will automatically close the <p> tag, breaking the rest of the page. So, even though conceptually (and visually) the content of the sidenote is a block, it has to be inside an inline element.

The solution was to use an inline HTML element <small> or <span> for the sidenote content.

<span class="sidenote-number">
  <small class="sidenote">
    sidenote content
  </small>
</span>
Code Snippet 1: Sidenote HTML template

The sidenote element that’s referenced few times in this post is the <small> HTML element with sidenote class.

Basic CSS #

Now to point (iii) from Kenneth’s blog ..

Here are my some key takeaways from his blog post:

  1. Use float: right;  I used float: left; because I have the Table of Contents in the right margin. I am going down the untrodden path of putting sidenotes in the left margin. to move the sidenote content to stick to the right side of its parent HTML element.
  2. Use width: 20vw;  “1vw” = 1% of the width of the viewport, and “viewport” is the browser window size. to limit the width of the sidenotes in the margin. We don’t want the sidenotes to overflow into the body text.
  3. Use margin-right: -22vw;  I used margin-left instead. to shift the whole sidenotes containing HTML element to outside the body.
  4. And finally, do not use the <aside> HTML element to contain the sidenote content—Use an inline element like <small> instead.

Now I’ll dive into the details of the CSS code. Please bear with me because I am not a web developer. These notes are mainly to document the CSS for myself and to share what I learned. So I would welcome any suggestions and corrections.

Sidenote CSS #

This is the basic CSS that puts the sidenote element in the margin to the left of its container element.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
.sidenote {
    font-size: 80%;
    position: relative;
}
/* Wide viewport */
@media (min-width: 1400px) {
    .sidenote {
        float: left;
        clear: left;
        margin-left: -23vw;
        text-align: right;

        top: -3rem;
        width: 20vw;
        margin-top: 1rem;
    }
}
Code Snippet 2: Sidenote CSS for normal or wide viewport
  • Line 2 : Sidenote content font size is set to be slighter smaller than the default body font size.

  • Line 3 : The position specified later using top is relative to the position of the element.

  • Line 6 : For this website, when the browser window is wider than 1400px, it’s considered as wide viewport. The sidenotes are shown in the margin only for wide viewports.

  • Line 8 : The whole sidenote element is floated to the left of the parent container.

  • Line 9 : This is necessary to prevent adjacent sidenotes from overlapping. This blog post puts it nicely:

    If an element can fit in the horizontal space next to the floated elements, it will. Unless you apply the clear property to that element in the same direction as the float. Then the element will move below the floated elements.

  • Line 10 : The float moves the sidenote element to the left most side of the container. But the margin is still further left to that container’s left border. So the margin-left: -<val>; shifts the sidenote element <val> units further left to the container’s left border.

  • Line 11 : As the sidenote is floating in the left margin, I wanted the text to the aligned towards  Towards the right ⇶ in wide viewport, but it’s left-aligned in narrow viewport as you’ll see in the CSS snippet for narrow viewport. the body text.

  • Line 14 : The width limits the width of the “sidenotes column” that gets created in the left margin.

  • Line 15 : The margin_top: 1rem; is just an embellishment tweak that inserts a 1rem space between adjacent sidenotes if they happen to get packed too close vertically.

CSS for mobile view or narrow viewport #

On a narrow viewport like a phone, we cannot afford to display a wide margin just for showing the sidenotes. So I interleave the sidenotes within the body, but with some indentation on the left (line 9 below).

As the sidenotes are within the body, they are still floated to the left, but now the text is aligned to the left instead of right. The width is set to 100% so that the text following the sidenote gets pushed to below it.  This part feels like a hack. So if someone can suggest a canonical way to deal with this, please let me know.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/* Narrow viewport */
@media (max-width: 1400px) {
    .sidenote {
        float: left;
        text-align: left;

        width: 100%;
        margin: 1rem 0;
        padding-left: 15%;
    }
}
Code Snippet 3: Sidenote CSS for mobile or narrow viewport

Sidenote Counter CSS #

The sidenote counters are implemented using just CSS.  Thanks to this codepen by a user dredmorbius and the CSS on takeonrules.com!

Let’s look at the sidenote HTML once again, but this time with some annotation to help understand how the counter number placement works in the body text and next to the sidenote in the margin.

<span class="sidenote-number">❶.sidenote-number  INCREMENT COUNTER
  <small class="sidenote">❷.sidenote::before     COUNTER IN MARGIN
    sidenote content
  </small>❸.sidenote-number::after               REF COUNTER IN BODY
</span>
Code Snippet 4: Sidenote HTML with annotation

The sidenote counters need to be reset in the body element so that they always begin from 1 on each page.

From the above annotated HTML, ❶ is the .sidenote-number element that wraps both of the sidenote counter locations: one in the body which acts as sidenote reference, and another in the margin next to the sidenote. So the counter is incremented only once at each .sidenote-number element.

❷ is the point where the .sidenote::before CSS rule will render the counter number before the sidenote content. As the .sidenote element is pushed into the margin, this counter number will also be pushed out along with the sidenote.

And finally, ❸ is the point that still remains at its original place within the main body. This is where the refernce counter value gets rendered. The .sidenote-number::after CSS rule is responsible for this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* Sidenote counter */
body {
    counter-reset: sidenote-counter;
}
.sidenote-number {
    counter-increment: sidenote-counter;
}
/* Counter before the sidenote in the margin. */
.sidenote::before {
    content: counter(sidenote-counter)".";
    position: relative;
    vertical-align: baseline;
    font-size: 0.9em;
    font-weight: bold;
}
/* Counter in the main body. */
.sidenote-number::after {
    content: counter(sidenote-counter);
    vertical-align: super;
    font-size: 0.7em;
    font-weight: bold;
    margin-right: 0.5rem;
}
Code Snippet 5: Sidenote Counter CSS

This final CSS snippet is responsible for highlighting the corresponding sidenote in the margin when mouse is hovered over a sidenote reference counter number in the body.

@media (min-width: 1400px) {
    /* Highlight the sidenote when mouse hovers on the sidenote number in body. */
    .sidenote-number:hover .sidenote {
        background-color: yellow;
    }
}
Code Snippet 6: Sidenote CSS Highlight

Things to improve #

While floating the sidenotes to the left moves them into the left margin, that aligns the left border of all the sidenotes to the left side of the page.

What I really wanted to do is to:

  1. Move the sidenotes into the left margin.
  2. Align the right border of all the sidenotes in a straight line with the left border of the body text.1

Conclusion #

That said, I am quite happy with the way the sidenotes and the counters turned out.

More References #

Apart from the references already linked in this post, here are few other references for creating sidenotes using CSS that I came across during my research:


  1. I already tried changing float: left; to float: right; and margin-left: -23vw; to margin-right: 50vw; but that has an undesirable effect what I don’t understand why—Now the sidenotes occupy the entire vertical space, even the part in the body. ↩︎