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 🙏

© 2026 – Pkg Stats / Ryan Hefner

sidenotes

v2.0.1

Published

[![sidenotes on npm](https://img.shields.io/npm/v/sidenotes.svg)](https://www.npmjs.com/package/sidenotes) [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/curvenote/sidenotes/main/LICENSE) ![CI](https://github.com/cu

Downloads

308

Readme

sidenotes

sidenotes on npm MIT License CI demo

Position floating sidenotes/comments next to a document with inline references.

Goals

  • Place notes/comments to the side of a document with inline references.
  • When an inline reference is clicked, animate the relevant sidenote to be as close as possible and move non-relevant sidenotes out of the way without overlapping.
  • Do not provide UI or impose any styling, only placement.

Use cases

  • Comment streams next to a document. This is showing Curvenote, which is a scientific writing platform that connects to Jupyter. Comments Using Sidenotes

Stack

  • React 18/19 with useReducer + Context (no Redux dependency)
  • TypeScript 6
  • Vite 8 for the demo, tsc for the library
  • Bun as the package manager

Demo

See demo/index.tsx for the full example.

bun install
bun run dev

sidenotes

Getting started

bun add sidenotes
# or: npm install sidenotes

Usage

Wrap the content that contains sidenotes in a <SidenotesProvider>. Put inline references inside <InlineAnchor> and the floating sidenote cards inside <Sidenote>. <AnchorBase> is an optional fallback target used when no inline anchor is mounted.

import {
  SidenotesProvider,
  Sidenote,
  InlineAnchor,
  AnchorBase,
  useSidenotes,
} from 'sidenotes';

function Doc() {
  const { deselect } = useSidenotes();
  return (
    <article onClick={deselect}>
      <AnchorBase anchor="anchor">
        Content with <InlineAnchor sidenote="note-1">an inline reference</InlineAnchor>.
      </AnchorBase>
      <div className="sidenotes">
        <Sidenote sidenote="note-1" base="anchor">
          Your custom UI, e.g. a comment.
        </Sidenote>
      </div>
    </article>
  );
}

export default function App() {
  return (
    <SidenotesProvider padding={10}>
      <Doc />
    </SidenotesProvider>
  );
}

The useSidenotes() hook

useSidenotes() is the public surface for interacting with sidenotes imperatively. It is backed by a stable control context and does not re-render when the selection changes — read the current selection by calling the getter functions.

const {
  getSelectedSidenote, // () => string | null
  getSelectedAnchor,   // () => string | null
  selectSidenote,      // (sidenoteId: string) => void
  selectAnchor,        // (anchor: string | HTMLElement) => void
  deselect,            // () => void
  reposition,          // () => void — recompute positions (e.g. after layout change)
} = useSidenotes();

If you need to re-render a component when the selection changes, read the state directly from context — the getters are intentionally decoupled from React's re-render loop.

Everything else (reducer, action creators, selectors, dispatch) is internal.

Styling

The library does not ship any CSS. Components render with stable class names so you can style them however you want:

| Component | Element / class | | -------------- | ---------------------------------------------- | | InlineAnchor | <span class="anchor [selected]"> | | AnchorBase | <div class="[selected]"> | | Sidenote | <div class="sidenote [selected]"> | | Container | whatever wraps your <Sidenote> list (e.g. <div className="sidenotes">) |

The demo's demo/index.css has a full working Tailwind v4 setup you can copy as a starting point.

Constraints

  • Sidenotes positioning is computed relative to a wrapping <article> element.
  • Each <SidenotesProvider> owns one document. Use multiple providers if you need more than one.
  • InlineAnchor renders a <span>; AnchorBase renders a <div>; Sidenote renders a <div>.

Development

bun install
bun run dev           # demo with HMR at http://localhost:3013
bun run build         # type-check + emit library to dist/
bun run build:demo    # build the demo site to dist-demo/
bun run typecheck
bun run lint
bun run format
bun run render-check  # build the demo and assert React output in happy-dom

Roadmap

  • Better mobile layout that places notes at the bottom.