npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

remark-marginnotes

v0.1.5

Published

A Remark plugin to handle margin note (side note) definitions and references.

Downloads

20

Readme

remark-marginnotes

npm version

A Remark plugin to parse inline footnote definitions and references, transforming them into nodes suitable for creating accessible margin notes, often styled like Tufte sidenotes. Includes Rehype handlers for HTML conversion.

Currently only works with multi-word definitions

this works:
[+note]: margin note here

this does not:
[+note]: something

Content

What this is

This package provides a unified (Remark) plugin that finds footnote definitions written inline immediately following their first reference. Standard Markdown footnotes require definitions to be placed at the bottom of the document. This plugin allows a syntax like:

Some text with a reference [+note1].
[+note1]: This is the definition for the first note. It appears right here in the source.

Some more text, maybe referencing the same note again [+note1] or a new one [+note2].
[+note2]: This is the second note.

It transforms these into custom MDAST nodes (asideFootnoteReference, asideFootnoteDefinition). When used with remark-rehype and the included handlers, it generates HTML suitable for styling as inline tooltips, sidenotes, or margin notes.

Install

npm install remark-marginnotes

Use

import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkGfm from 'remark-gfm';
import { remarkMarginnotesPlugin, marginnoteHandlers } from 'remark-marginnotes';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
import { VFile } from 'vfile';

const markdown = `
# Example Document

This document demonstrates the inline aside footnotes. Here's the first reference [+note1].

[+note1]: This is the definition for the first note. It can contain *Markdown* like emphasis and \`code\`.

Here's a second, different reference [+ref-abc].

[+ref-abc]: This definition belongs to the second reference.

We can still have normal[^fn] footnotes.
[^fn]: By normal, we mean gfm style footnotes

And there you go!
`;

async function processMarkdown() {
    try {
        // Read the example Markdown file
        const file = await read(path.join(__dirname, 'example.md'));
        console.log('--- Input Markdown ---');
        console.log(String(file));

        // Process the file
        const result = await unified()
            .use(remarkParse)
            .use(remarkGfm)
            .use(remarkMarginnotesPlugin)
            .use(remarkRehype, {
                handlers: marginnoteHandlers
            })
            .use(rehypeStringify)
            .process(file);

        console.log('\n--- Output HTML ---');
        console.log(String(result));

    } catch (error) {
        console.error("Error processing Markdown:", error);
    }
}

processMarkdown();

Syntax

  • Reference: [+identifier]
    • identifier consists of letters, numbers, underscores (_), or hyphens (-). Example: [+note-1], [+figure_a].
  • Definition: [+identifier]: Definition text...
    • Must start at the beginning of a paragraph.
    • Must match an identifier used in a reference before or in the same paragraph.
    • The definition text starts after the colon (:), optionally separated by whitespace.
    • The definition includes all content in the paragraph after the colon, including inline Markdown (like emphasis or code).

Example HTML Output

Given the Markdown in the Use section, the approximate HTML output would be:

<h1>Example Document</h1>
<p>This document demonstrates the inline aside footnotes. Here's the first reference <sup class="marginnote-ref-wrapper"><a href="#marginnote-def-note1" id="marginnote-ref-note1-1" class="marginnote-ref" role="doc-noteref" aria-describedby="marginnote-label-note1" data-marginnote-identifier="note1" data-marginnote-instance="1">note1</a></sup><span id="marginnote-def-note1" class="marginnote-def" role="note" data-marginnote-identifier="note1"><span id="marginnote-label-note1" class="hidden">Marginnote note1</span><span class="marginnote-number">note1</span>This is the definition for the first note. It can contain <em>Markdown</em> like emphasis and <code>code</code>. <a href="#marginnote-ref-note1-1" class="marginnote-backref" role="doc-backlink" aria-label="Back to first reference for marginnote note1">↩</a></span>.</p>
<p>Here's a second, different reference <sup class="marginnote-ref-wrapper"><a href="#marginnote-def-ref-abc" id="marginnote-ref-ref-abc-1" class="marginnote-ref" role="doc-noteref" aria-describedby="marginnote-label-ref-abc" data-marginnote-identifier="ref-abc" data-marginnote-instance="1">ref-abc</a></sup><span id="marginnote-def-ref-abc" class="marginnote-def" role="note" data-marginnote-identifier="ref-abc"><span id="marginnote-label-ref-abc" class="hidden">Marginnote ref-abc</span><span class="marginnote-number">ref-abc</span>This definition belongs to the second reference. <a href="#marginnote-ref-ref-abc-1" class="marginnote-backref" role="doc-backlink" aria-label="Back to first reference for marginnote ref-abc">↩</a></span>.</p>
<p>We can still have normal<sup><a href="#user-content-fn-fn" id="user-content-fnref-fn" data-footnote-ref aria-describedby="footnote-label">1</a></sup> footnotes.</p>
<p>And there you go!</p>
<section data-footnotes class="footnotes"><h2 class="sr-only" id="footnote-label">Footnotes</h2>
  <ol>
    <li id="user-content-fn-fn">
      <p>By normal, we mean gfm style footnotes <a href="#user-content-fnref-fn" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
    </li>
  </ol>
</section>

Styling

This plugin outputs semantic HTML with specific classes and ARIA attributes, but provides no CSS. You need to style it yourself.

  • .marginnote-ref-wrapper: The <sup> wrapping the reference link.
  • .marginnote-ref: The <a> link for the reference number (e.g., 1).
  • .marginnote-def: The <span> containing the definition.
  • .marginnote-number: The <span> containing the number within the definition (e.g., 1.).
  • .marginnote-backref: The <a> link () inside the definition pointing back to the first reference.
  • .hidden: Class for the accessible label inside the definition span. You should hide this visually (e.g., using common sr-only CSS techniques).
article > * {
    width: 75%; /* Limit top level blocks to 75% because margin notes will take right 25% */
}

.marginnote-ref-wrapper {
    background: white;
}
.marginnote-ref-wrapper > a {
    color: black;
}

.marginnote-def {
  float: right;
  clear: right;
  margin-right: -30%;
  width: 25%;
  margin-top: 0.3rem;
  line-height: 1.3;
  vertical-align: baseline;
  position: relative;
  padding-bottom: 0.5em;
  font-size: 0.9em;
  border-bottom: 1px dotted #666;
  margin-bottom: 2em;
}

.marginnote-def::before {
  content: "";
  position: absolute;
  top: calc(29px);
  left: 0;
  width: 100%;
  height: 0;
  border-bottom: 1px dotted #666;
}

.marginnote-number {
  display: block;
  width: 30px;
  height: 30px;
  line-height: 30px;
  text-align: center;
  border: 1px dotted #666;
  border-bottom: 0;
  margin-bottom: 0.5em;
}

.marginnote-backref {
  margin-left: 0.5em;
  text-decoration: none;
}

.hidden {
    display: none;
}

Options

type Options = {
    label: 'text' | 'numbers' | 'shapes' | 'letters' | 'custom';
    charList?: string[];
}

Example Usage

const result = await unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkMarginnotesPlugin)
.use(remarkRehype, { handlers: marginnoteHandlers({
    label: 'custom',
    charList: ['†', '‡', '§']
  })
})
.use(rehypeStringify)
.process(file);

label

  • text: Use the definition text as the label.
  • numbers: Use sequential numbers (1, 2, 3...).
  • shapes: Use shapes like , , .
  • letters: Use letters (a, b, c...).
  • custom: Use a custom character list defined in charList.

charList

If you choose custom for the label option, you can provide a charList array with your own characters. The characters will be used in the order they appear in the list, cycling through if there are more references than characters.