Vertical rhythm in two columns

The backdrop is ruled with the root element's baselines, one every 1rlh. Body text with nothing else in it sits on these lines by itself. Adding headings, images of arbitrary height, quotes with a border and other elements knocks the text that follows off the lines, and two neighbouring cards stop matching line for line.

block-step: 1rlh margin-box start rounds the outer size of such blocks up to a multiple of the step, and the text after them lands on the lines again, the same way in both columns. No browser supports it yet, so the technique switcher adds three ways of getting the same result today next to the native mode: a script, a scroll-driven animations hack, and calc-size(). What each of them costs is described below the columns.

Rhythm technique

Rhythm and images

Even the first headings of the two columns, by taking a different number of lines, already knock the paragraphs after them off the rhythm, which is especially visible next to the neighbouring column. Further down one column has an illustration and the other a quote, and their heights differ.

The image above is fitted to the rhythm by the same rule: nine lines plus 1cap, bottom on a baseline, the excess cropped.

A subheading further down

The subheading is set in a different size, and its line is taller than a body line. Without rounding it also shifts everything after it by a fraction of a line.

Rhythm and headings that wrap onto two lines

The heading of this card takes two lines, the heading of the neighbouring one takes one. That alone is enough for the columns to drift apart from here on.

An image should stand with its bottom on a baseline, and its height should be a whole number of lines plus the cap height. Then the rectangle of the image and the rectangle of the text opposite feel the same size.

The eye reads the top of a text block along the cap line: almost every first line starts with a capital, and ascenders often support that line too. That is why the image gets 1cap rather than 1ex, and the excess is cropped with cover fitting.

A subheading somewhere else

The image on the left follows this rule: three lines plus 1cap, bottom on a baseline. Its outer size is deliberately not a multiple of the step, short by exactly the distance from the top of the line to the baseline, and rounding makes up that remainder. For a floated image the cost of an error is higher: until the remainder is made up, both the lines beside it and the lines below it land on different baselines.

no rhythm
Plain CSS: headings, images and quotes have their own heights and margins. This is also how block-step would look in a browser that does not understand it.
native: block-step
block-step: 1rlh margin-box start on every rhythm breaker, one line and no wrappers. So far it only works in a WebKit build with the cssRhythmicSizingEnabled flag on; in other browsers this mode changes nothing.
script
The same computation in JavaScript: a ResizeObserver plus a recalculation of margins on every resize and after fonts load. The script reads --block-step-size, --block-step-insert, --block-step-align and --block-step-round from the block, measures the outer size of the box together with its own margins, and distributes the difference by the rules of the editor's draft. This is what authors have to write today, and at the same time an “implementation” the arithmetic of the WPT tests is checked against.
scroll-driven animations

Not a line of JavaScript in the layout. The height of every rhythm breaker is measured by a view timeline: two animations on the same timeline with different ranges yield two numbers, and the difference between them is the size of the element. The subject is the block's own ::before, stretched over its whole padding-box. Then round(up, …) rounds the size up to 1rlh, and the difference goes into the margins: below, or split evenly between the two sides, depending on --bs-align. The price:

calc-size()

block-size: calc-size(auto, round(up, size, 1rlh)) rounds the block's own height up to a multiple of the step. No wrappers and no script, but it still falls short of block-step: