Achieving Perfect Vertical Rhythm in Web Browsers
Vertical rhythm has fascinated me since 2015, when my friend Rob Turlinckx introduced me to it. I wanted the baselines of a web page to sit on one grid, as they do in print, and it took me years to get there in a browser. Every baseline on this page now lands on a row boundary of that grid, with 8px rows on desktop and 4px rows on mobile.
In my early experiments, I could align headings and paragraphs within isolated sections, such as feature cards, but I could not keep one rhythm across an entire page. I approximated by eye, tweaking margins and padding until things looked right. It was slow and never exact.
The turning point came in early 2020, when I found the Plumber project. It introduced me to the baseline ratio, the piece of the puzzle I had been missing. Building on Plumber, I wrote rhythm-sass, which sets the typography on this page and uses the baseline ratio to place every baseline on the grid. I have used it in several production projects since.
The model behind the baseline ratio comes from a passage in the CSS 2.1 specification:
Still for each glyph, determine the leading L to add, where L = ‘line-height’ - AD. Half the leading is added above A and the other half below D, giving the glyph and its leading a total height above the baseline of A’ = A + L/2 and a total depth of D’ = D + L/2.
In this diagram, the striped background represents the A is D is AD and centers it within the A + D as the
A real font’s A and D come from its metrics tables and usually add up to more than 1em. Noto Serif, the typeface of this page, has an ascent of 1.069em and a descent of 0.293em, which would give a ratio of 0.215 if A + D were the font-size, as in the diagram. The browser centers all 1.362em in the line-height instead, which puts the baseline (line-height - A - D) / 2 + A below the top of the line box. Equating that with a 1em box whose ratio is r gives r = (1 + D - A) / 2, or 0.112 for Noto Serif. rhythm-sass calculates this ratio from the two metrics, which npx rhythm-sass reads from a font file. It also accepts a measured ratio, such as those Plumber publishes.
With a font’s baseline ratio, font-size and line-height, we can calculate the exact distances from the baseline to the top and bottom of the line-height. Type sizes on this page come from a modular scale of ratio 1.125 over 16px. A desktop paragraph sets step 2, 20.25px, on a 32px line-height, which puts its first baseline 23.857px below the top of its line box, and baseline-top in rhythm-sass adds 0.143px of padding to move it to the 24px line. This site leaves that remainder to the browser’s rounding instead, as described below. Since every line-height is a whole number of rhythm units, the lines that follow stay on the grid.
The same arithmetic sets a drop cap without the CSS initial-letter property. The drop-cap mixin in rhythm-sass calculates the capital’s font-size and line-height from the font metrics, floats it and offsets it with position: relative so that it spans from the cap line of the first line down to the baseline of a given line. The initial at the top of this post is set this way and spans three lines. The rhythm-sass documentation covers this mixin and the rest of the API.
Browsers round the baseline differently. Measured on macOS, Chromium rounds the ascent and descent to whole pixels and floors half an odd leading, Firefox rounds them up and WebKit keeps the exact value. All three put desktop body text on the 24px line, but at step 3 of the scale, 22.78px, Chromium places the baseline at 24px and Firefox at 25px. A few sizes on this site therefore sit just off the scale, such as step 3.075, where all three agree. The initial at the top gets the same treatment: on desktop, it is half a pixel larger than the size the mixin solves, which Chromium would paint a pixel high.
rhythm-gpui has one renderer to match instead of three. It reads A and D through gpui’s text system, solves the same equation in floating point and lets gpui’s layout snap each edge to a device pixel, which keeps each edge within half a device pixel of its exact position.
Vertical rhythm is no longer something I approximate by eye. Once the baseline ratio is known, every space on the page follows from it, and the grid toggle in the first paragraph shows the result. If you have questions about rhythm-sass or want to compare notes on web typography, I’d be glad to hear from you.