BaselineGrid
Vertical rhythm where every line in adjacent columns sits on the same rule, including the lines under a photograph. Baseline, and the page in the frame below is built with it.
Scroll inside the frame. Press Show grid, bottom right, to see the rules. 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.
-
--base-line1.5rem - The unit. Every vertical measurement on the page is a whole number of these, so changing it rescales the whole rhythm at once without anything drifting out of step.
-
--base-size1rem - Body type size. Its line box is exactly one baseline, which is the fact the rest of the system is built on.
-
--base-rulergba(54, 54, 48, 0.16) - The overlay colour. Fainter than a column overlay needs to be, because unlike one this covers every line of text on the page rather than the spaces between columns.
-
--base-measure66ch - The widest a line of body copy gets. A baseline grid does nothing for readability if the lines are too long for the eye to track back to the next one.
What it gets right
- Everything is a multiple of one number: line heights, margins, the space above a heading and below it. The moment one value is chosen by eye the rhythm is gone, and it is gone for everything after it on the page rather than just for that element
- Pictures are the hard part and they are why most baseline grids are a lie. An image is whatever height its proportions make it, which is almost never a whole number of baselines, and one of them puts every line below it off the rule for the rest of the page. Either crop it to a multiple, which is the default here, or let it run free and put the rhythm back underneath it
- The free-height escape hatch is corrected in the browser rather than ignored, because the error is not known until the image has been laid out at a width nobody chose. A margin cannot fix a number that does not exist yet
- The first baseline is what has to land, not the box. A heading with a tall line-height has space above the letters that pushes the text off the rule while the box itself looks correct
- The overlay is a repeating gradient here, which would be wrong for a column grid and is right for this one: a baseline is an even repeat with no gutter to get wrong, so the gradient cannot disagree with the layout
- A heading takes two baselines above and one below, because a heading belongs to what follows it rather than to what it interrupts
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.
/*
* BaselineGrid: Beamish
* https://beamish.ink/grids/baseline-grid
*
* Vertical rhythm. Every line of type in two adjacent columns sits on the same
* invisible rule, the way it does on a printed spread, and the overlay draws
* those rules so you can see whether it is true.
*
* Three things carry it, and the second is where almost every attempt at this
* falls over.
*
* Everything is a multiple of one number. Line heights, margins, the space
* above a heading and below it: all of them `calc(var(--base-line) * n)`. The
* moment one value is chosen by eye the rhythm is gone, and it is gone for
* everything after it on the page rather than just for that element.
*
* Pictures are the hard part, and they are why most baseline grids are a lie.
* An image is whatever height its own proportions make it, which is almost
* never a whole number of baselines, and one of them puts every line below it
* off the rule for the rest of the page. There are only two honest answers:
* give the image a height that is a multiple and crop to it, or let it take its
* own height and put the rhythm back underneath it. This does the first by
* default, with `.g-plate--free` for the second, and the second is corrected
* rather than ignored.
*
* And the first line is the one that has to land. A block's rhythm is set by
* where its first baseline sits, not by its box: a heading with a tall
* line-height has space above the letters that pushes the text off the rule
* while the box looks right. The trim is done with padding derived from the
* line-height, which is the only part of this that has to be arithmetic rather
* than a round number.
*/
:root {
/*
* The unit. Everything on the page is a whole number of these, so changing
* it rescales the whole rhythm without anything drifting.
*/
--base-line: 1.5rem;
/* Body type. Its line-height must be exactly one baseline. */
--base-size: 1rem;
--base-rule: rgba(54, 54, 48, 0.16);
--base-measure: 66ch;
}
/*
* Every text element gets a line box that is a whole number of baselines. This
* is the whole system; the rest is applying it.
*/
.g-rhythm {
font-size: var(--base-size);
line-height: var(--base-line);
}
.g-rhythm :where(p, ul, ol, blockquote, pre, figure, table) {
margin-block: 0 var(--base-line);
}
.g-rhythm :where(p, li) {
max-width: var(--base-measure);
}
/*
* Headings take whole baselines too, and the space above one is a baseline
* more than the space below it, because a heading belongs to what follows.
*/
.g-rhythm :where(h1, h2, h3, h4) {
margin-block: calc(var(--base-line) * 2) var(--base-line);
font-weight: 700;
}
.g-rhythm h1 {
font-size: calc(var(--base-line) * 2);
line-height: calc(var(--base-line) * 2);
}
.g-rhythm h2 {
font-size: calc(var(--base-line) * 1.15);
line-height: calc(var(--base-line) * 1.5);
}
.g-rhythm :where(h3, h4) {
font-size: var(--base-size);
line-height: var(--base-line);
}
/*
* A picture cropped to a whole number of baselines. The number is a property
* rather than a fixed value, so a plate can be four baselines in a sidebar and
* twelve across a page and both still land on the rule.
*/
.g-plate {
--plate-lines: 8;
display: block;
width: 100%;
height: calc(var(--base-line) * var(--plate-lines));
object-fit: cover;
margin-block: 0 var(--base-line);
}
/*
* The escape hatch: a picture at its own proportions, with the rhythm put back
* underneath it.
*
* `margin-bottom` cannot fix this, because the error is not known until the
* image has been laid out at a width nobody chose. The round-up is done in the
* browser, against the element's real height, which is the only place the
* number exists.
*/
.g-plate--free {
height: auto;
margin-bottom: 0;
}
.g-plate--free + .g-catch {
display: block;
/* Set by the script below. Zero until then, so nothing jumps on load. */
height: var(--catch, 0px);
}
/* Columns that share the rhythm, so two of them line up line for line. */
.g-columns {
display: grid;
grid-template-columns: repeat(var(--base-columns, 2), minmax(0, 1fr));
column-gap: calc(var(--base-line) * 2);
}
/*
* The overlay. Drawn from `--base-line` itself, so it cannot describe a rhythm
* other than the one in force.
*
* A repeating gradient is right here, unlike the column overlay, because a
* baseline really is an even repeat with no gutter to get wrong.
*/
.g-baseline-rules {
position: fixed;
inset: 0;
z-index: 60;
pointer-events: none;
background-image: repeating-linear-gradient(
to bottom,
var(--base-rule) 0,
var(--base-rule) 1px,
transparent 1px,
transparent var(--base-line)
);
}
/*
* Said again here, because the attribute's display:none lives in the browser's
* stylesheet and any author display beats it. Without this the overlay ships
* switched on with its own button insisting it is off.
*/
.g-baseline-rules[hidden] {
display: none;
}
@media (max-width: 48rem) {
.g-columns {
grid-template-columns: minmax(0, 1fr);
}
}