
What Is a Vertical Rhythm, and How Do You Build One?
- Sajjad
- Typography
- 01 Oct, 2026
You inherit a blog template where every gap was set by eye. Paragraphs have margin-bottom: 18px, headings have margin-top: 37px, lists sit 12px from the text above, images get 20px, and blockquotes use 1.3em. Each value made sense to whoever typed it, but scrolling through a long article feels jittery. Nothing is obviously wrong, yet the page lacks the calm, even cadence of a well-printed book. What's missing is vertical rhythm: a single spacing unit that every vertical measurement is built from.
This article explains what vertical rhythm is, why it improves reading, and how to build one in modern CSS using a base line height, the lh and rlh units, spacing tokens in Tailwind CSS v4, and a few newer properties that make the job easier than it used to be. It also covers where strict rhythm stops being worth the effort and how to keep it compatible with accessibility requirements.
What Is Vertical Rhythm?
Vertical rhythm is the practice of making all vertical spacing on a page, including line heights, margins, padding, and the heights of elements, a multiple of one base unit. That base unit is usually the line height of your body text.
If your body text is 16px with a line height of 1.5, the line height is 24px. In a page with vertical rhythm:
- Paragraphs are separated by 24px (one unit).
- Headings take up 48px of line height and have 48px above and 24px below them (multiples of the unit).
- Images, code blocks, and blockquotes have heights and margins that are multiples of 24px, or at least margins that are.
The result is a consistent cadence. Lines of text in a sidebar line up with lines in the main column, sections feel evenly spaced, and the reader's eye moves down the page at a steady pace.
Rhythm vs. Baseline Grid
There are two levels of strictness:
| Approach | What lines up | Effort |
|---|---|---|
| Spacing rhythm | All margins and gaps are multiples of the unit | Low |
| Baseline grid | Every line of text sits on the same invisible horizontal grid, across columns | High |
A baseline grid comes from print, where software like InDesign snaps every line's baseline to a grid. On the web, a true baseline grid is fragile. Fonts place text differently within the line box, images have arbitrary heights, and users change font sizes. A spacing rhythm gives most of the benefit with a fraction of the maintenance. That's what this article focuses on, with notes on how to get closer to a true grid where it matters.
Why Vertical Rhythm Matters
- It makes structure readable. When spacing is consistent, differences in spacing carry meaning. A larger gap above a heading clearly signals a new section because ordinary gaps are uniform.
- It reduces design debt. One unit and a handful of multiples replace dozens of one-off values. New components inherit the rhythm by default.
- It improves multi-column layouts. Text in adjacent columns lines up, which looks deliberate rather than accidental.
- It speeds up design decisions. "Two units or three?" is a faster question than "28px or 31px?"
It's closely related to a modular type scale: the scale controls font sizes, and the rhythm controls the space those sizes sit in.
Step 1: Choose the Base Unit
Start with body text, because it makes up most of the page.
html {
font-size: 100%; /* respect the user's browser setting, usually 16px */
line-height: 1.5;
}
body {
font-size: 1.125rem; /* 18px at default settings */
}
A unitless line height between 1.4 and 1.7 works for most body text. If you're unsure, start at 1.5, which is also the value WCAG 1.4.12 (Text Spacing) uses as its test threshold. The post on leading and line height explains how to fine-tune it for your font and line length.
Your rhythm unit is the computed line height of body text. With the CSS above, that's 18px × 1.5 = 27px.
Step 2: Express the Unit in CSS
The Old Way: Custom Properties
Before the line-height units, you had to calculate the unit yourself:
:root {
--body-size: 1.125rem;
--body-leading: 1.5;
--rhythm: calc(var(--body-size) * var(--body-leading));
}
p,
ul,
ol,
blockquote,
pre,
figure {
margin-block: 0 var(--rhythm);
}
This works everywhere, and because it uses rem, it scales with the user's font size preference.
The Modern Way: lh and rlh Units
CSS now has two units built for this:
lhequals the computed line height of the element itself.rlhequals the computed line height of the root element (html).
Both are supported in current Chrome, Edge, Firefox, and Safari. rlh is the one you want for a page-wide rhythm, because it stays the same no matter which element you use it on. To make rlh match your body text, set the body's font size and line height on the root:
html {
font-size: 112.5%; /* 18px at default settings */
line-height: 1.5; /* 1rlh = 27px */
}
p,
ul,
ol,
blockquote,
pre,
figure,
table {
margin-block: 0 1rlh;
}
lh is useful inside components, for example to give an icon the same height as a line of the text next to it:
.inline-icon {
block-size: 1lh;
inline-size: auto;
}
Step 3: Fit Headings to the Rhythm
Headings are larger than body text, so their single-line height will rarely equal one unit. The trick is to set each heading's line height to a whole number of units, rather than a ratio.
h1,
h2,
h3,
h4 {
margin-block: 2rlh 1rlh;
text-wrap: balance;
}
h1 {
font-size: 2.5rem; /* 45px */
line-height: 2rlh; /* 54px */
}
h2 {
font-size: 1.875rem; /* about 34px */
line-height: 1.5rlh; /* about 40px */
}
h3 {
font-size: 1.375rem; /* about 25px */
line-height: 1rlh; /* 27px */
}
h4 {
font-size: 1.125rem;
line-height: 1rlh;
}
Half units (1.5rlh) are a common compromise. Purists insist on whole units, but a half-unit step still keeps everything on a predictable grid, and it lets you avoid headings whose line height is either cramped or far too loose.
Also make sure that margin above a heading is larger than the margin below it, so the heading is visually attached to the content it introduces. A 2:1 ratio (2rlh above, 1rlh below) is a good default.
Watch Multi-Line Headings
A heading line height that's perfect for one line can be too loose when it wraps. If 2rlh makes two-line H1s look disconnected, step down to 1.5rlh. Rhythm is a guide, and a heading that reads as one unit matters more than exact grid alignment.
Step 4: Build Spacing Tokens from the Unit
Define every layout spacing value as a multiple of the unit, then use only those tokens.
:root {
--space-quarter: 0.25rlh;
--space-half: 0.5rlh;
--space-1: 1rlh;
--space-2: 2rlh;
--space-3: 3rlh;
--space-4: 4rlh;
}
.card {
padding: var(--space-1);
}
.section {
padding-block: var(--space-4);
}
Quarter and half units are fine for tight UI spacing like button padding. Keep the text-level spacing (paragraphs, headings, blocks) on whole and half units.
Tailwind CSS v4
Tailwind v4 generates its spacing utilities from a single --spacing variable, so you can tie every p-*, m-*, and gap-* utility to your rhythm. With a 27px rhythm, a quarter-unit base means mt-4 equals one line:
@import "tailwindcss";
@theme {
--spacing: 0.25rlh;
}
@layer base {
html {
font-size: 112.5%;
line-height: 1.5;
}
}
Now mb-4 is 1rlh, mt-8 is 2rlh, and py-16 is 4rlh. The whole team gets rhythm-aligned spacing without memorizing anything. Tailwind's Preflight already sets line-height: 1.5 on html, but declaring it in your base layer makes the dependency explicit: if someone changes the root line height, every spacing utility changes with it. More on configuration is in typography in Tailwind CSS v4.
Step 5: Use Flow Spacing Instead of Margins Everywhere
Applying margin-bottom to every element creates a trailing gap at the end of each container. A cleaner pattern is flow spacing: put the space only between siblings.
.flow > * + * {
margin-block-start: var(--flow-space, 1rlh);
}
.flow > :is(h2, h3) {
--flow-space: 2rlh;
}
.flow > :is(h2, h3) + * {
--flow-space: 0.5rlh;
}
Apply .flow to your article body, and every child gets one unit of space above it, headings get two, and the element right after a heading sits closer to it. No element has a trailing margin to clean up.
Step 6: Handle Images, Embeds, and Code Blocks
Text naturally fits the rhythm, but media doesn't. An image with an intrinsic height of 413px will push everything below it off the grid. You have three options, in increasing order of strictness:
- Accept it. Keep margins on whole units and let the media height be whatever it is. For most sites, this is the right choice.
- Use rhythm-friendly aspect ratios. Set a fixed aspect ratio and let width drive height. Some heights will land on the grid; others won't.
- Round media heights to the unit. CSS
round()is supported in all modern browsers and can snap a computed height to a multiple of1rlh:
.article img {
inline-size: 100%;
block-size: round(up, calc(100cqi * 9 / 16), 1rlh);
object-fit: cover;
}
.article {
container-type: inline-size;
}
This sizes images at roughly 16:9 relative to the container width, then rounds the height up to the next full line. The object-fit: cover hides the slight crop.
Code blocks are easier: their line height should be a multiple or clean fraction of the rhythm, and their padding should be in units.
pre {
font-size: 0.875em;
line-height: 1rlh;
padding: 0.5rlh 1rlh;
}
Step 7: Trim Leading Above and Below Text
Line boxes include half-leading above and below the text, so a paragraph's visible top edge sits a few pixels below its box's top. That's why text inside a button or card often looks lower than it should, and why spacing measured to text looks bigger than the number in your CSS.
The text-box property trims that extra space to the font's cap height or alphabetic baseline:
h1,
h2,
.card-title {
text-box: trim-both cap alphabetic;
}
This is supported in Chromium-based browsers and Safari. It's progressive enhancement: unsupported browsers ignore it and keep the normal line box. Use it on headings and UI components where alignment matters most, and test that the change doesn't break your rhythm. Trimming changes the element's height, so a trimmed heading will no longer have an exact whole-unit height.
Debugging Your Rhythm
Overlay the grid while you work. This background draws a line at every unit:
.show-rhythm {
background-image: repeating-linear-gradient(
to bottom,
transparent 0,
transparent calc(1rlh - 1px),
rgb(255 0 0 / 0.25) calc(1rlh - 1px),
rgb(255 0 0 / 0.25) 1rlh
);
}
Add show-rhythm to body in development and scroll through a long article. Text baselines won't sit exactly on the lines, because fonts position text differently within the line box, but each line should have the same relationship to the grid all the way down.
Keeping Rhythm Accessible
WCAG 1.4.12 (Text Spacing) requires that no content or functionality is lost when users override text spacing to line height 1.5×, paragraph spacing 2× the font size, letter spacing 0.12em, and word spacing 0.16em. WCAG 1.4.4 requires text to resize to 200% without loss of content.
A rhythm based on rem and rlh handles both well, because everything scales with the user's settings. Problems come from fixed heights. Never set a fixed height on a text container to keep it on the grid; use min-block-size instead, so text can grow when users increase spacing or size.
Vertical Rhythm FAQ
A spacing rhythm is worth it on almost every site because it reduces arbitrary values and makes structure clearer. A strict baseline grid is only worth the effort for editorial or multi-column layouts where line alignment across columns is visible.
The lh unit equals the computed line height of the element it is used on, while rlh equals the line height of the root html element. Use rlh for a page-wide rhythm and lh for sizing things relative to a specific element's lines.
Use the line height of your body text, typically between 1.4 and 1.7 times the font size. A value of 1.5 is a safe starting point and matches the line height used in WCAG text spacing tests.
Not necessarily. Keeping margins around images on whole units is usually enough. If you need exact alignment, the CSS round function can snap image heights to a multiple of the rhythm unit, with object-fit cover handling the small crop.
No, as long as you build it from relative units. If the root font size or line height changes at a breakpoint, every rlh-based value updates automatically, so the rhythm stays consistent at every size.
No. Put more space above a heading than below it, typically twice as much, so the heading visually belongs to the content that follows. Equal spacing makes headings float between sections.
Conclusion
Vertical rhythm means deriving all vertical spacing from one unit, normally the line height of your body text. It turns dozens of arbitrary margins into a small, predictable system, makes the structure of a page easier to read, and keeps new components consistent with old ones.
Modern CSS makes it far easier than it used to be. Set your body size and line height on the root, use rlh for margins and heading line heights, build spacing tokens (or Tailwind's --spacing) from fractions of the unit, use flow spacing between siblings, and decide deliberately how strictly media should align. Keep everything in relative units and avoid fixed heights, and the rhythm will hold up at any zoom level or text spacing setting.
Here are some useful references for going deeper on vertical rhythm:
- MDN Web Docs: length — reference for CSS length units, including
lhandrlh. - MDN Web Docs: round() — the CSS stepped-value function used to snap sizes to a unit.
- MDN Web Docs: text-box — trimming half-leading above and below text.
- W3C: Understanding WCAG 2.2 Success Criterion 1.4.12: Text Spacing — the spacing overrides your rhythm must survive.
- Tailwind CSS Docs: Theme variables — how
--spacingand text tokens drive generated utilities.


