Color mode

Sidenotes

Tufte-style side notes for Riebeckite. Authors keep writing ordinary GFM footnotes ([^1] and [^1]: text); the plugin rewrites the generated footnote markup into an inline reference plus a note that renders as a margin note on desktop and as a tap-open popover on mobile.

日本語

Overview

The plugin registers a rehype plugin that rewrites the footnote markup the core pipeline produces from remark-gfm:

  • Each footnote reference becomes a sup with a small reference link ([data-rr-sidenotes-ref]) that keeps its href="#fn-…" target, so the footnote stays reachable with plain browser navigation.
  • Each footnote definition becomes a .rr-sidenotes__note aside next to the reference: a static margin note on desktop (@media (min-width: 48rem)) and a popover on mobile (@media (max-width: 48rem)).
  • The trailing footnote definitions section is kept (it is the link target on mobile and the no-JavaScript fallback) and hidden on desktop, where the margin notes are always visible.

A small client entry (initSidenotes) toggles the mobile popover: tap to open/close, Escape to close, and click outside to close. It ignores the page when (min-width: 48rem) matches — desktop margin notes need no JavaScript.

Usage

ts
import { defineConfig } from "@riebeckite/core";
import { sidenotes } from "@riebeckite/plugin-sidenotes";
 
export default defineConfig({
  // ...
  plugins: [sidenotes()],
});

Write normal GFM footnotes:

md
Riebeckite renders margin notes at the side of the text.[^1]
 
[^1]: The note text appears beside the reference on desktop and in a popover on mobile.

Options

Option Type Default Description
className string "" Extra CSS class on each sidenote root
ariaLabel string "Sidenote" Accessible name prefix for each note ("Sidenote 1")
openLabel string "Footnote" Accessible label prefix for a closed reference ("Footnote 1")
closeLabel string "Close sidenote" Accessible label prefix for an open reference ("Close sidenote 1")
popoverAlignment "bottom" | "end" "bottom" Where the mobile popover is anchored
ts
sidenotes({
  ariaLabel: "Note",
  popoverAlignment: "end",
});

Output

html
<p>
  Riebeckite renders margin notes here.<sup class="rr-sidenotes__ref">
  <a href="#user-content-fn-1" id="user-content-fnref-1" class="rr-sidenotes__toggle"
     data-rr-sidenotes-ref aria-expanded="false" aria-controls="rr-sidenotes-1"
     aria-label="Footnote 1">1</a></sup>
</p>
<aside class="rr-sidenotes__note rr-sidenotes__note--popover-bottom" id="rr-sidenotes-1"
       data-rr-sidenotes-note aria-label="Sidenote 1" tabindex="-1">
  <span class="rr-sidenotes__index" aria-hidden="true">1</span>
  <div class="rr-sidenotes__body"><p>The note text appears beside the reference.</p></div>
</aside>

Accessibility

  • The reference is a real link (href="#user-content-fn-…") with aria-expanded and aria-controls; the client toggles aria-expanded and swaps the label between openLabel and closeLabel.
  • The popover is labelled with aria-label and receives focus (tabindex="-1") when opened via a script.
  • Without JavaScript, tapping the reference jumps to the footnote definitions as in ordinary GFM output.

Style

The package ships style.css. Register it like any other plugin stylesheet:

ts
import "@riebeckite/plugin-sidenotes/style.css";

Stable hooks follow the rr-sidenotes convention: rr-sidenotes__toggle, rr-sidenotes__note, rr-sidenotes__index, rr-sidenotes__body, rr-sidenotes__footnotes, plus the rr-sidenotes__note--open modifier.

Exports

  • sidenotes(options?) — plugin factory
  • sidenotesPlugin — alias of sidenotes
  • rehypeSidenotes(options?) — the rehype transformer
  • resolveSidenotesOptions(options?) — apply option defaults
  • renderSidenotesReference(input) / renderSidenotesNote(input) — HTML builders
  • initSidenotes(options?) — client popover initializer
  • Types: SidenotesOptions, ResolvedSidenotesOptions, SidenotesClientOptions

Limitations

  • Footnotes that reuse the same definition share one margin note and popover.
  • The footnote definitions section is hidden on desktop; margins must have room for the notes, and themes can restyle .rr-sidenotes__note freely.
  • Notes inside tables or deeply nested inline markup are placed after the nearest block ancestor, so their vertical position is approximate.

See also

History

1 changesCollapseExpand
1 + <!-- Generated from packages/plugins/sidenotes/README.md. Do not edit this page directly; edit the package README and run `pnpm docs:sync`. -->
2 +
3 + # Sidenotes
4 +
5 + Tufte-style side notes for Riebeckite. Authors keep writing ordinary GFM
6 + footnotes (`[^1]` and `[^1]: text`); the plugin rewrites the generated
7 + footnote markup into an inline reference plus a note that renders as a margin
8 + note on desktop and as a tap-open popover on mobile.
9 +
10 + [日本語](./sidenotes.md)
11 +
12 + ## Overview
13 +
14 + The plugin registers a rehype plugin that rewrites the footnote markup the
15 + core pipeline produces from `remark-gfm`:
16 +
17 + - Each footnote reference becomes a `sup` with a small reference link
18 + (`[data-rr-sidenotes-ref]`) that keeps its `href="#fn-…"` target, so the
19 + footnote stays reachable with plain browser navigation.
20 + - Each footnote definition becomes a `.rr-sidenotes__note` aside next to the
21 + reference: a static **margin note** on desktop (`@media (min-width: 48rem)`)
22 + and a **popover** on mobile (`@media (max-width: 48rem)`).
23 + - The trailing footnote definitions section is kept (it is the link target
24 + on mobile and the no-JavaScript fallback) and hidden on desktop, where the
25 + margin notes are always visible.
26 +
27 + A small client entry (`initSidenotes`) toggles the mobile popover: tap to
28 + open/close, `Escape` to close, and click outside to close. It ignores the page
29 + when `(min-width: 48rem)` matches — desktop margin notes need no JavaScript.
30 +
31 + ## Usage
32 +
33 + ```ts
34 + import { defineConfig } from "@riebeckite/core";
35 + import { sidenotes } from "@riebeckite/plugin-sidenotes";
36 +
37 + export default defineConfig({
38 + // ...
39 + plugins: [sidenotes()],
40 + });
41 + ```
42 +
43 + Write normal GFM footnotes:
44 +
45 + ```md
46 + Riebeckite renders margin notes at the side of the text.[^1]
47 +
48 + [^1]: The note text appears beside the reference on desktop and in a popover on mobile.
49 + ```
50 +
51 + ## Options
52 +
53 + | Option | Type | Default | Description |
54 + | ------ | ---- | ------- | ----------- |
55 + | `className` | `string` | `""` | Extra CSS class on each sidenote root |
56 + | `ariaLabel` | `string` | `"Sidenote"` | Accessible name prefix for each note (`"Sidenote 1"`) |
57 + | `openLabel` | `string` | `"Footnote"` | Accessible label prefix for a closed reference (`"Footnote 1"`) |
58 + | `closeLabel` | `string` | `"Close sidenote"` | Accessible label prefix for an open reference (`"Close sidenote 1"`) |
59 + | `popoverAlignment` | `"bottom" \| "end"` | `"bottom"` | Where the mobile popover is anchored |
60 +
61 + ```ts
62 + sidenotes({
63 + ariaLabel: "Note",
64 + popoverAlignment: "end",
65 + });
66 + ```
67 +
68 + ## Output
69 +
70 + ```html
71 + <p>
72 + Riebeckite renders margin notes here.<sup class="rr-sidenotes__ref">
73 + <a href="#user-content-fn-1" id="user-content-fnref-1" class="rr-sidenotes__toggle"
74 + data-rr-sidenotes-ref aria-expanded="false" aria-controls="rr-sidenotes-1"
75 + aria-label="Footnote 1">1</a></sup>
76 + </p>
77 + <aside class="rr-sidenotes__note rr-sidenotes__note--popover-bottom" id="rr-sidenotes-1"
78 + data-rr-sidenotes-note aria-label="Sidenote 1" tabindex="-1">
79 + <span class="rr-sidenotes__index" aria-hidden="true">1</span>
80 + <div class="rr-sidenotes__body"><p>The note text appears beside the reference.</p></div>
81 + </aside>
82 + ```
83 +
84 + ## Accessibility
85 +
86 + - The reference is a real link (`href="#user-content-fn-…"`) with `aria-expanded`
87 + and `aria-controls`; the client toggles `aria-expanded` and swaps the label
88 + between `openLabel` and `closeLabel`.
89 + - The popover is labelled with `aria-label` and receives focus (`tabindex="-1"`)
90 + when opened via a script.
91 + - Without JavaScript, tapping the reference jumps to the footnote definitions
92 + as in ordinary GFM output.
93 +
94 + ## Style
95 +
96 + The package ships `style.css`. Register it like any other plugin stylesheet:
97 +
98 + ```ts
99 + import "@riebeckite/plugin-sidenotes/style.css";
100 + ```
101 +
102 + Stable hooks follow the `rr-sidenotes` convention: `rr-sidenotes__toggle`,
103 + `rr-sidenotes__note`, `rr-sidenotes__index`, `rr-sidenotes__body`,
104 + `rr-sidenotes__footnotes`, plus the `rr-sidenotes__note--open` modifier.
105 +
106 + ## Exports
107 +
108 + - `sidenotes(options?)` — plugin factory
109 + - `sidenotesPlugin` — alias of `sidenotes`
110 + - `rehypeSidenotes(options?)` — the rehype transformer
111 + - `resolveSidenotesOptions(options?)` — apply option defaults
112 + - `renderSidenotesReference(input)` / `renderSidenotesNote(input)` — HTML builders
113 + - `initSidenotes(options?)` — client popover initializer
114 + - Types: `SidenotesOptions`, `ResolvedSidenotesOptions`, `SidenotesClientOptions`
115 +
116 + ## Limitations
117 +
118 + - Footnotes that reuse the same definition share one margin note and popover.
119 + - The footnote definitions section is hidden on desktop; margins must have
120 + room for the notes, and themes can restyle `.rr-sidenotes__note` freely.
121 + - Notes inside tables or deeply nested inline markup are placed after the
122 + nearest block ancestor, so their vertical position is approximate.
123 +
124 + ## See also
125 +
126 + - [Plugin guide](../reference/plugin-api.en.md)
127 +