Search by

phil / footnotes-sidenotes

phil

Footnotes and margin notes for UJO (Universal JSON Output) documents: references and notes resolved in reading order, numbered and checked, written as accessible endnotes, as sidenotes with CSS only, or at the foot of the printed page in CSS GCPM

0.1.0-alpha 2026-11-11 06:11 UTC

This package is auto-updated.

Last update: 2026-11-11 13:30:28 UTC


README

Footnotes and margin notes for UJO (Universal JSON Output) documents, on screen and in print.

phil/footnotes-sidenotes works on the notes a UJO document already holds: footnote blocks and the <sup data-footnote="id"> references of its text. A resolver walks the document once, collects both, numbers the notes in reading order — restarting per document, per chapter or per page marker, in decimal, roman numerals or symbols, per locale — and reports what is wrong as typed results: a reference without a note, a note never referenced, a duplicate identifier, a reference inside a note, a note referenced several times.

The caller then chooses where the notes go. At the end of the document or of each section, as a labelled region with pairs of links between reference and note. Beside the text, as margin notes: a column on wide viewports, an inline toggle on narrow ones, with CSS only. At the foot of the printed page, in CSS GCPM, for a print engine that floats footnotes.

It is for anything long enough to need notes: a book, a game rulebook, a documentation page. It is a plain PHP library on phil/ujo, keeps nothing between calls, and ships two small stylesheets that dress semantic HTML with PHIL UX classes.

The text of a note always goes through the inline sanitizer of phil/ujo, never raw. Identifiers carry a prefix, so several documents can share a page.

Status

Part State
NoteResolver, Resolution, Note, NoteReference, Problem, ProblemKind Implemented and tested
NoteOptions, Restart, Numbering: decimal, roman, symbols, per locale Implemented and tested
Html\NotesRenderer, Placement: end of document, end of section, margin, page foot Implemented and tested
NoteLabels: English, French, any other from the application Implemented and tested
Text\NotesPlainText: references and notes as plain text Implemented and tested
Stylesheet: the screen and print sheets, the per-page count of the engine Implemented and tested

Documentation

Document What it covers
Installation Requirements, installing, serving the stylesheets.
Resolving notes Reading order, restarts, numbering styles and locales, the problems reported.
Placements The four placements and the HTML each one writes.
Accessibility Link pairs, identifiers, regions, roles, labels, the keyboard and reduced motion.
Styling The classes, the tokens, the margin column and the narrow toggle.
Print CSS GCPM, labels in the call and the marker, white space, restarts per natural page.
Plain text The form for a search index or an accessibility check.
Extension points What an application chooses, completes or relies on.
Testing The library's tests, benchmarks, fuzz target and mutation bar.
Reference Every type of the library, with its role.

Quick start

composer require phil/footnotes-sidenotes
use Phil\FootnotesSidenotes\Html\NotesRenderer;
use Phil\FootnotesSidenotes\NoteLabels;
use Phil\FootnotesSidenotes\NoteOptions;
use Phil\FootnotesSidenotes\NoteResolver;
use Phil\FootnotesSidenotes\Numbering;
use Phil\FootnotesSidenotes\Placement;
use Phil\FootnotesSidenotes\ProblemKind;
use Phil\FootnotesSidenotes\Restart;
use Phil\Ujo\Html\HtmlRenderer;
use Phil\Ujo\Ujo;

$document = new Ujo()->decode($json);

$resolver = new NoteResolver(new NoteOptions(
    restart: Restart::Chapter,
    locales: ['fr' => Numbering::symbols()],
));
$notes = $resolver->resolve($document, 'fr-CA');

foreach ($notes->problemsOf(ProblemKind::MissingNote) as $problem) {
    // $problem->noteId, $problem->blockId
}

$renderer = new NotesRenderer(HtmlRenderer::default(), NoteLabels::forLocale('fr-CA'));

echo $renderer->render($document, $notes, Placement::Margin, idPrefix: 'chapter-3');

Requirements

PHP 8.5 or later with ext-hash and ext-mbstring, and phil/ujo.

Contributing

Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.

License

MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Parler Haut Interagir Librement project. See LICENSE.