Footnotes on the page
Picture a policy report with 40 sources. Readers want to see each source at the bottom of the page they are reading, not make a trip to the back. That is a footnote: a short note at the bottom of the page that cites it. The hard part is not drawing the note but fitting the rest of the page around it.
In CSS, a footnote is an element that floats to the footnote area. You write the note where it belongs in the text, and the renderer moves it: a call (the small number) stays in its place, and the note body goes to the bottom of the page that holds the call.
<p>
Spending rose by 4% in 2025<span class="note">National
Statistics Office, Annual Accounts, table 3.2.</span>
while revenue stayed flat.
</p> .note { float: footnote; font-size: 8.5pt; }
::footnote-call { content: counter(footnote); vertical-align: super; font-size: 70%; }
::footnote-marker { content: counter(footnote) ". "; }
@page {
@footnote {
border-top: 0.5pt solid #999;
padding-top: 2mm;
max-height: 40%;
}
} Palatine places these notes in paginated PDFs and fits the body text around them. A long note takes space from the body; several notes can leave little room for the paragraph that cites them. A note too long for its page is split between lines and continues at the top of the next footnote area. max-height on @footnote caps how much of a page the notes may take.
By default notes are numbered through the whole document. Some styles restart at 1 on every page, which keeps the numbers short in a long book:
@page { counter-reset: footnote; } When a call falls on the last line of a page and its note does not fit, something has to give. footnote-policy decides: auto keeps the line on its page as long as the note can at least start there, line moves the line to the next page with its note, and block moves the whole paragraph. Test a dense page, a note longer than a paragraph, and a call on the last line.
Notes from Markdown and pandoc
Notes rarely start life as CSS. A Markdown converter such as pandoc writes them as endnotes: a numbered list at the end, with each call linking to its note and a back link to the text. Palatine can turn those into page footnotes for you with the linkedFootnotes option (--linked-footnotes in the CLI). Calls marked role="doc-noteref", or pandoc's footnote-ref class, have their note moved to the call; back links are removed and the emptied endnotes section is hidden.
await renderer.render({
html: pandocOutput, css, format: "pdf", width: 794, height: 1123,
options: { pagination: true, linkedFootnotes: true }
}); Keep numbering consistent. If notes are written by hand, one skipped number throws off every note after it. Let the footnote counter number them, or generate the numbers before rendering. Palatine does not run JavaScript, so a script on the page cannot fix them afterwards.
Outside paginated PDF, in continuous PDF, SVG, PNG and WebP, there are no pages to put notes at the bottom of. Footnote bodies then appear as endnotes after the content, so a single template still works in every format.
Cross-references that stay true
A cross-reference is a phrase such as “see Table 3” or “see section 2.4”. In a manual with a few hundred of them, the risk is stale references: someone inserts a section and half the numbers are wrong. Treat reference text as generated output, not something typed by hand.
Page references, such as “see page 42”, depend on the final layout. Palatine supports target-counter() for page numbers and other counters, and target-text() for text from the referenced element. Write an ordinary link to a stable id, and let CSS fill in the rest:
<p>Details are in <a class="xref" href="#methodology"></a>.</p>
...
<h2 id="methodology">Methodology</h2> a.xref::after {
content: "“" target-text(attr(href url)) "”, page "
target-counter(attr(href url), page);
} This renders as “Details are in “Methodology”, page 12.” Palatine lays the document out again until the page numbers settle, since inserting a number can itself push content to another page. In the PDF the link is also clickable and jumps to the heading. A link to an id that does not exist produces a crossref.missing-target warning, so broken references show up in your build log rather than in print.
Tables of contents and bookmarks
A table of contents is a list of cross-references. Palatine can generate one: an element with the data-palatine-toc attribute is filled with a list of links to the headings of the document, before styles apply. Add page numbers with the same target-counter() rule:
.palatine-toc a::after {
content: " · " target-counter(attr(href url), page);
}
.palatine-toc .toc-level-2 { padding-left: 1.5em; } Dot leaders, the row of dots between a title and its page number, are drawn with leader() in some engines; Palatine does not support it yet, so imitate them in CSS if your design needs them.
PDF bookmarks, the outline readers see in the side panel of their PDF viewer, are separate from the printed contents. Headings do not become bookmarks unless you ask:
h1 { bookmark-level: 1; }
h2 { bookmark-level: 2; bookmark-state: closed; } What to check
Render the finished document and follow ten references by hand, including forward references and ones across page breaks. Then check:
- a page with four or five notes, and a note long enough to continue on the next page;
- a call on the last line of a page, under each
footnote-policyyou consider; - the table of contents against the actual page numbers, after the last edit to the text;
- the bookmark panel of a PDF viewer, and the warnings of the render for missing targets.
Explore the rendering documentation