MarginaliaColumns
A column of text with a narrow margin beside it, each note held level with the paragraph it belongs to. Marginalia, and the page in the frame below is built with it.
Scroll inside the frame. Open it on its own for the full width.
The properties are the interface
Override these in your own stylesheet rather than editing the file, so the next version is a clean replacement.
-
--marg-measure34rem - The text column. Its own measure, independent of the note column, because the two are different kinds of reading and want different line lengths.
-
--marg-note13rem - The note column, as a fixed width rather than a fraction. Give it a fraction and notes get wider as the window does, which is backwards: a note is a short aside and wants a short line.
-
--marg-gutterclamp(1.25rem, 3vw, 3rem) - Between the text and its notes. Wide enough that a note reads as marginal rather than as a second column of equal standing.
-
--marg-step1.5rem - The vertical gap between blocks. It is also what the level modifier cancels, so changing it keeps a levelled note aligned.
-
--marg-rulergba(54, 54, 48, 0.2) - The rule down the margin, used by the ruled modifier and by the narrow layout where the note sits under its paragraph.
What it gets right
- A note is a grid item in the same row as its paragraph, and that is the whole trick. Float it and it sits wherever the text happened to reach; position it absolutely and it holds still while the paragraph reflows to a different height, so the note explaining the third sentence ends up beside the fifth. As a grid row the pair cannot come apart, at any width, with no measurement and no script
- The note column is a fixed measure rather than a fraction. Two independent widths, not one width split two ways: the text keeps its measure and the note keeps its own short line
- The note aligns to the top of its row. Stretched or centred, a two-line note beside a nine-line paragraph sits halfway down and stops looking like an annotation of the first sentence, which is what it is
- The note must follow the element it annotates in the source. That is what makes it read correctly to a screen reader and what lets the narrow layout put it directly underneath
- Below 54rem it becomes one column with the note indented and ruled under its paragraph, so it still reads as an aside rather than as the next paragraph
The stylesheet
This is the whole item. Copy it into your project, link it, and use the class names. Nothing to install and nothing to mount.
/*
* MarginaliaColumns: Beamish
* https://beamish.ink/grids/marginalia-columns
*
* A column of text with a narrow margin beside it for notes, each note level
* with the paragraph it belongs to. The layout of a scholarly edition, and of
* most good documentation.
*
* Three things carry it, and the first is the one that breaks in every version
* built with floats or absolute positioning.
*
* A note is a grid item in the same row as its paragraph. That is the whole
* trick. Float it and it sits where the text happens to have reached; position
* it absolutely and it holds still while the paragraph above it reflows to a
* different height, so the note that explained the third sentence ends up
* beside the fifth. As a grid row, the pair cannot come apart: the browser
* keeps them level at every width, with no measurement and no script.
*
* The note column is a fixed measure, not a fraction. Give it `1fr` of
* anything and the notes get wider as the window does, which is exactly
* backwards: a note is a short aside and wants a short line, while the text
* beside it has its own measure to keep. Two independent widths, not one split
* two ways.
*
* And the note aligns to the top of its row, not the middle. `align-self:
* start` is doing real work: stretched, a two-line note next to a nine-line
* paragraph centres itself halfway down and stops looking like an annotation
* of the first sentence, which is what it is.
*/
:root {
/* The text column. Its own measure, independent of the note's. */
--marg-measure: 34rem;
/* The note column. A fixed width, because a note wants a short line. */
--marg-note: 13rem;
--marg-gutter: clamp(1.25rem, 3vw, 3rem);
--marg-step: 1.5rem;
--marg-rule: rgba(54, 54, 48, 0.2);
}
/*
* Text, then note. `auto` on the first track rather than `1fr`, so the text
* takes its measure and the pair sits together instead of being pushed apart
* by a window that happens to be wide.
*/
.g-marg {
display: grid;
grid-template-columns: minmax(0, var(--marg-measure)) var(--marg-note);
column-gap: var(--marg-gutter);
row-gap: var(--marg-step);
align-items: start;
margin-inline: auto;
width: fit-content;
max-width: 100%;
padding-inline: clamp(1rem, 4vw, 3rem);
box-sizing: border-box;
}
/* Anything in the text column. Explicit, because source order alone would put
* a second paragraph in the note column. */
.g-marg > :where(p, h1, h2, h3, ul, ol, blockquote, figure) {
grid-column: 1;
margin: 0;
}
/*
* The note. Column two of its own row, aligned to the top of it.
*
* It must come immediately after the element it annotates in the source, which
* is also what makes it read correctly to a screen reader and what lets the
* narrow layout put it directly underneath.
*/
.g-note {
grid-column: 2;
align-self: start;
margin: 0;
font-size: 0.78em;
line-height: 1.55;
color: var(--label-3, #6b6b63);
}
/*
* A note that should hang level with the previous paragraph rather than start
* its own row. Grid puts each item in a new row by default, so a note placed
* after a paragraph gets its own row and sits below it; this pulls it back up.
*/
.g-note--level {
grid-row: auto / span 1;
margin-top: calc(var(--marg-step) * -1);
}
/* A rule down the margin, between the text and its notes. */
.g-marg--ruled > .g-note {
border-left: 1px solid var(--marg-rule);
padding-left: calc(var(--marg-gutter) / 2);
margin-left: calc(var(--marg-gutter) / -2);
}
/* Something that ignores the margin and runs the full width of both. */
.g-marg > .g-full {
grid-column: 1 / -1;
}
.g-marg > .g-full img {
display: block;
width: 100%;
height: auto;
}
/*
* Narrow. One column, and the note falls directly under what it annotates,
* which is why the source order matters. Indented and ruled so it still reads
* as an aside rather than as the next paragraph.
*/
@media (max-width: 54rem) {
.g-marg {
grid-template-columns: minmax(0, 1fr);
width: auto;
}
.g-marg > :where(p, h1, h2, h3, ul, ol, blockquote, figure),
.g-note {
grid-column: 1;
}
.g-note,
.g-marg--ruled > .g-note {
margin-top: 0;
margin-left: 0;
padding-left: 1rem;
border-left: 2px solid var(--marg-rule);
}
}