Skip to content
WANCRAFT

C.161Typography & Text

Sidenotes

Numbered references whose notes sit in the margin beside their line, and fold into inline disclosures on narrow containers.

NewReactCSSTailwindadvanced

Overview

Sidenotes brings the book margin to the web. Wrap an article in <Sidenotes> and mark any phrase with <Sidenote note={…}>. On wide containers each note is placed in the margin column, aligned with the line that holds its reference; when two notes would overlap, the later one steps down so they stack cleanly, and the article grows if the last note runs long. The switch is driven by the container's own width (ResizeObserver), not the viewport, so the same article works in a sidebar or a full page. Below the breakpoint every reference becomes a disclosure button that expands its note inside the paragraph. Hovering or focusing a reference highlights its note and vice versa. Reference marks and notes share a CSS counter, so numbering is always consistent, including in server-rendered HTML.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/sidenotes.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/sidenotes.json

Method 02 · Copy and paste

  1. 1. No extra dependencies — React and Tailwind CSS only.
  2. 2. Copy the source from the Code tab into components/ui/sidenotes.tsx.
  3. 3. Import it and adjust props.
Dependencies
None
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { Sidenotes, Sidenote } from "@/components/ui/sidenotes";

export function SidenotesExample() {
  return (
    <Sidenotes className="max-w-3xl text-lg leading-relaxed">
      <p>
        A sidenote waits{" "}
        <Sidenote note="Aligned with the line that holds its reference.">
          beside the words
        </Sidenote>{" "}
        it explains, so the reader never has to leave the line.
      </p>
    </Sidenotes>
  );
}

03Props

PropTypeDefaultDescription
Sidenotes.breakpointnumber720Container width in px from which notes sit in the margin; narrower containers use inline disclosures.
Sidenotes.gutternumber200Width of the margin column in px.
Sidenotes.gapnumber40Space between the text and the margin column in px.
Sidenotes.side"right" | "left""right"Which margin holds the notes.
Sidenotes.numbering"decimal" | "lower-alpha" | "lower-roman""decimal"Counter style shared by reference marks and notes.
Sidenotes.spacingnumber12Minimum vertical space between stacked notes in px.
Sidenotes.accentstring"#FF7448"Reference marks, highlight and focus ring colour.
Sidenote.noteReactNode—Required. The note (phrasing content: text, links, emphasis).
Sidenote.childrenReactNode—Required. The referenced words in the body text.
Sidenote.defaultOpenbooleanfalseStart expanded in the inline (narrow) layout.

04Features

  • 01Margin notes aligned to the line of their reference, measured after fonts load
  • 02Collision avoidance: overlapping notes stack downward with consistent spacing; the article grows to fit the last note
  • 03Container-driven layout (ResizeObserver on the article), not viewport media queries
  • 04Inline disclosure notes with an animated grid-rows expand on narrow containers
  • 05Two-way highlight: hover/focus a reference to light its note, hover a note to light its reference
  • 06Numbering from a CSS counter (decimal, alpha or roman), identical in marks and notes and correct in SSR
  • 07Left or right margin; positions are written to the DOM directly, with no re-render on resize
motion · tweeninput · hoverinput · focusinput · clickinput · keyboard

05Accessibility

  • Each reference mark is a real <button> named "Note 1", "Note 2"… (visually hidden "Note" plus the counter).
  • Margin layout: the button has aria-describedby pointing at its note, so focusing it announces the note.
  • Inline layout: the button is a disclosure with aria-expanded and aria-controls; Enter/Space toggles and Escape closes the note.
  • Notes have role="note"; collapsed inline notes are visibility-hidden, so they are skipped by assistive tech and the tab order.
  • Focus-visible draws an accent outline; the highlight follows keyboard focus as well as the pointer.
  • Reduced motion removes the expand and highlight transitions.

06Limitations

  • Notes render inside the paragraph, so they should contain phrasing content only (no block elements).
  • Very dense notes can push later notes far below their references; keep margin notes short.