phil / footnotes-sidenotes
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
Package info
github.com/parler-haut-interagir-librement/footnotes-sidenotes
pkg:composer/phil/footnotes-sidenotes
Requires
- php: >=8.5
- ext-hash: *
- ext-mbstring: *
- phil/ujo: ^0.1@dev
Requires (Dev)
- phpunit/phpunit: ^13.4
Suggests
- phil/paged-media: To turn the print output into a PDF
- phil/typography: To set the text of notes with the typography of a locale
Provides
None
Conflicts
None
Replaces
None
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. |
| 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.