Margin footnotes
Margin footnotes present ordinary Markdown footnotes beside the paragraph or heading they qualify. Heine checks the Markdown relationship and supplies semantic, local note markup. Your template and CSS decide whether a note opens below prose or in a wide-screen margin.
This is an opt-in presentation for one declared Markdown input on a page. It
does not add another Markdown syntax or change the ordinary endnote
presentation of unselected Markdown sources. Every ordinary [^label]
reference still needs a matching definition, as described in the
reference.
Start with one page
Enable the existing Markdown extension and name a Fluent message for the
accessible note controls. Heine supplies its $number argument when it formats
that message:
# heine.toml
[markdown]
extensions = ["footnotes"]
[markdown.margin_footnotes]
marker_message = "footnote-marker"# locales/en/site.ftl
footnote-marker = Footnote { $number }
Select one declared, unsegmented Markdown input on a rendered page. An
unsegmented input is a declared Markdown file without <!-- segment: … -->
markers:
# content/en/guide/chronology.page
template = "guide.tera"
[content]
main = "main.md"
[footnotes]
content = "main"
Use standard Markdown references and definitions in main.md:
Heine rejects ambiguous local times.[^time]
[^time]: During an autumn daylight-saving transition, one wall-clock time can
name two different instants.
Render the selected source exactly once with the source-aware function. The
template may use page.footnotes to add a layout class without receiving
pre-rendered note HTML:
<article{% if page.footnotes %} class="has-margin-footnotes"{% endif %}>
{{ markdown(content="main") }}
</article>
Heine replaces the normal endnote section for that source with a numeric marker and an independent local note instance after its containing prose block. A repeated reference keeps its number but receives another local instance, so it does not send a reader back to an earlier paragraph.
The generated structure stays local to that block. The native checkbox is the
interactive control, its localized accessible name comes from marker_message,
and the note is both its controlled and described content. The visible number
inside the note repeats the marker's number, helping readers associate notes
when several are open:
<div class="heine-annotated-block">
<p>Heine rejects ambiguous local times.<sup class="heine-footnote-marker">
<label for="__heine-footnote-toggle-1">1</label>
</sup></p>
<input class="heine-footnote-toggle" id="__heine-footnote-toggle-1"
type="checkbox" aria-label="Footnote 1" aria-controls="__heine-footnote-note-1"
aria-describedby="__heine-footnote-note-1">
<aside class="heine-footnote" id="__heine-footnote-note-1" role="doc-footnote">
<span class="heine-footnote-number" aria-hidden="true">1</span>
<p>During an autumn daylight-saving transition, one wall-clock time can
name two different instants.</p>
</aside>
</div>
The identifiers are implementation details. Heine reserves the
__heine-footnote-… family for generated ordinary and margin-footnote
fragments, so its generated headings and footnotes cannot claim one another's
IDs. Trusted raw HTML and templates remain author-owned: Heine does not parse
them to check manually chosen id values against that reserved family.
A selected source does not need a footnote. This makes a directory default useful for a documentation section whose individual pages need notes only occasionally.
Add presentation CSS
Heine deliberately does not generate site styling. Without CSS, the native checkbox controls and their note asides remain visible in source order. This is a usable baseline.
The following small example hides notes until their own marker is selected,
then places an open note below its paragraph. It preserves a focus indication
when the browser supports :has():
.heine-footnote {
display: none;
}
.heine-footnote-toggle:checked + .heine-footnote {
display: block;
}
.heine-footnote-number {
float: inline-start;
margin-inline-end: 0.5em;
}
@supports selector(.heine-annotated-block:has(.heine-footnote-toggle:focus-visible)) {
.heine-footnote-toggle {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
.heine-annotated-block:has(.heine-footnote-toggle:focus-visible)
.heine-footnote-marker label {
outline: 2px solid currentcolor;
outline-offset: 2px;
}
}
@media print {
.heine-footnote-toggle {
display: none;
}
.heine-footnote {
display: block !important;
}
}
At a sufficiently wide breakpoint, a site can make .heine-annotated-block a
two-column grid and put .heine-footnote-toggle:checked + .heine-footnote in
the second column. The note stays in ordinary flow, so several open notes do
not overlap later prose. A tall note can leave space before the next block;
that is the deliberate CSS-only trade-off for dependable local placement.
For printing, keep every note visible after its annotated prose instead of
trying to force a page-margin layout.
The starter site contains a complete responsive and print-safe example.
What Heine checks
For a selected source, every reference needs exactly one definition, every definition needs a reference, and definitions must be unique. Errors identify the original Markdown location; duplicate definitions also identify the first one.
The first version accepts a reference in an ordinary Markdown paragraph or heading. It supports ordinary inline formatting around the marker. References in a list, table, block quote, code block, raw HTML, link, image, or another footnote definition are errors. A paragraph or heading containing both raw HTML and a reference is also rejected because Heine cannot safely reconstruct arbitrary HTML nesting into the local wrapper.
Definition bodies may contain normal block Markdown, including links, lists, code blocks, math, and copied-asset references. Their headings are ancillary note content, so they do not appear in a table of contents and receive no generated heading anchor.
The selected input must be rendered once with markdown(content="…"). A
missing call leaves no promised local-note structure; two calls duplicate
generated IDs. Heine reports either as a template error. The markdown filter
is still useful for arbitrary text, but it rejects the exact selected source.
Directory defaults
Documentation pages often use one main Markdown input. A descriptor can select it for a whole subtree:
# content/en/guide/_directory.toml
[pages.footnotes]
content = "main"
The nearest complete table wins. Tables do not merge. A page or nested
descriptor may write footnotes = false only to suppress an inherited setting.
Writing that value when nothing is inherited is a checked error, because it is
inactive structural policy rather than a useful fallback.
Likewise, [markdown.margin_footnotes] is a checked error unless at least one
rendered page has an effective setting. The named Fluent message is checked in
every locale that uses it, with the ordinary default-locale fallback.
Table of contents and builds
A table of contents and margin footnotes may select the same Markdown input.
One markdown(content="…") call then supplies both heading anchors and local
notes. A marker never contributes to a heading's visible ToC text or fragment.
Changing the selected Markdown file participates in the normal quick rebuild
behavior. Adding, removing, or changing a page or descriptor footnotes
setting changes a resolved relationship, so use a full build. heine serve
normally performs the complete rebuild required for that change.
For exact fields and template values, see the reference. The glossary defines the durable term used here.