Sidenotes / Marginnotes in Kirby

Here’s how I recently implemented sidenotes, following the original Tufte-CSS markup approach which I liked because it’s pretty clever and doesn’t require javascript.

I only did “sidenotes” (numbered footnotes), but “marginnotes” (floating content without numbers) should be pretty straight forward with the same approach.

First, I added a structure field sidenotes to my article blueprint, below the main blocks field:

sidenotes:
  type: structure
  label: Sidenotes
    fields:
      text:
        label: Text
        type: writer

Then created a plugin for the new Kirbytag (sn:<number>):

// site/plugins/tufte/index.php
Kirby::plugin('bruno/tufte', [
    'tags' => [
        'sn' => [
            'html' => function($tag) {
                $sidenotes = $tag->parent()->sidenotes()->toStructure();
                $return = '';
                if ($sidenotes 
                && $note = $sidenotes->nth($tag->value() - 1)) {
                    $text = $note->text()->kirbyText();
                    $text = preg_replace( '/^<[^>]+>|<\/[^>]+>$/', '', $text);
                    $return = '<label for="sn-' . $tag->value . '" class="sidenote-number"></label><input type="checkbox" id="sn-' . $tag->value . '" class="margin-toggle"/><span class="sidenote">' . $text . '</span>';
                    $return = $return;
                }
                return $return;
            }
        ],
    ],
],

It’s ugly, but it works and produces the following markup:

<label for="sn-1" class="sidenote-number"></label>
<input type="checkbox" id="sn-1" class="margin-toggle">
<span class="sidenote"> {{ SIDENOTE TEXT }} </span>

I included the original Tufte.css file, but added some attributes to make it more customizable – most notably the css variables in root which allow easily tailoring the layout:

/* assets/css/tufte.css */
:root {
    --tufte-width: 350px;
    --tufte-distance: 50px;
    counter-reset: sidenote-counter;
}

.sidenote {
    float: right;
    clear: right;
    width: var(--tufte-width);
    margin-right: calc(var(--tufte-width) * -1);
    margin-bottom: 1rem;
    padding-left: var(--tufte-distance);
    vertical-align: baseline;
    position: relative;
}

.sidenote-number {
    counter-increment: sidenote-counter;
}

.sidenote-number:after,
.sidenote:before {
    position: relative;
    vertical-align: baseline;
}

.sidenote-number:after {
    content: counter(sidenote-counter);
    font-size: .8rem;
    top: -0.5rem;
    left: 0.1rem;
}

.sidenote:before {
    content: counter(sidenote-counter) " ";
    font-size: .7rem;
    top: -0.5rem;
}

input.margin-toggle {
    display: none;
}

label.sidenote-number {
    display: inline;
}

label.margin-toggle:not(.sidenote-number) {
    display: none;
}

@media (max-width: 800px) {
    label.sidenote-number {
        display: inline;
        cursor: pointer;
    }

    .sidenote {
        display: none;
    }

    label.margin-toggle:not(.sidenote-number) {
        display: inline;
        font-size: 1.3rem;
        line-height: 1rem;
    }

    .margin-toggle:checked + .sidenote {
        display: block;
        float: left;
        left: 1rem;
        clear: both;
        width: 95%;
        margin: 1rem 2.5%;
        vertical-align: baseline;
        position: relative;
    }
}

Result:


:exclamation:Notes/Side effects:

  • It’s not semantic.
  • If you copy&paste text with this markup, it will always include the footnotes content mashed in right where they are registered with the (sn:) tag.
  • It’s (probably) not very accessible. Some javascript and aria-attributes should in fact be applied.
1 Like