Golden ratio typography with rem units

24mm · f/4 · 1/30s · ISO 800Credit: Greg Rickaby
Back in 2012, I opened the stylesheet for Twenty Twelve, WordPress's new default theme, and found font sizes in a unit called rem.
I had NO idea what a rem was. I thought R.E.M. was a band.
So I did what you do. I read everything, wrote this post in 2013, and told you to put html { font-size: 62.5% } at the top of every stylesheet. Thirteen years later, I don't do that anymore, and I'd like to explain why before you copy it out of a post with my name on it.
This is the rewrite. It still covers rem to px and golden ratio typography, but now there are five little machines to play with, and none of them ask you to take my word for anything. The first one is a whole calculator, because I know some of you came here for numbers, not my life story.
Golden ratio typography calculator
Tell it your body font size and how wide your text column is. It works out the rest: the line height, the best width for your size and the best size for your width, a heading scale where every level gets its own line height, spacing, and the CSS to paste into your site.
- Line height
- 26px (1.623)
- Best width for 16px
- 670.2px
- Best size for 680px
- 16.1px
- Width for a 26px line height
- 685.3px
Golden ratio typography
Every paragraph has three dimensions: font size, line height, and line width. Change one, and the other two have to move with it.
Wider lines need more room
Your eye sweeps back to the start of each line. The longer the trip, the more space it needs to land on the right one.
Headings get their own
Small print gets one too.
| Style | Size | Line height |
|---|---|---|
| h1 | 2.618rem 41.9px | 1.355 56.7px |
| h2 | 2.058rem 32.9px | 1.383 45.5px |
| h3 | 1.618rem 25.9px | 1.429 37px |
| h4 | 1.272rem 20.4px | 1.503 30.6px |
| body | 1rem 16px | 1.623 26px |
| small | 0.786rem 12.6px | 1.816 22.8px |
:root {
--content-width: 42.5rem; /* 680px */
--text-h1: 2.618rem; /* 41.9px */
--text-h1-line-height: 1.355;
--text-h2: 2.058rem; /* 32.9px */
--text-h2-line-height: 1.383;
--text-h3: 1.618rem; /* 25.9px */
--text-h3-line-height: 1.429;
--text-h4: 1.272rem; /* 20.4px */
--text-h4-line-height: 1.503;
--text-body: 1rem; /* 16px */
--text-body-line-height: 1.623;
--text-small: 0.786rem; /* 12.6px */
--text-small-line-height: 1.816;
--space-xs: 0.62rem; /* 9.9px */
--space-s: 1.003rem; /* 16px */
--space-m: 1.623rem; /* 26px */
--space-l: 2.625rem; /* 42px */
--space-xl: 4.248rem; /* 68px */
}
body {
font-size: var(--text-body);
line-height: var(--text-body-line-height);
}
.content {
max-width: var(--content-width);
}
h1 {
font-size: var(--text-h1);
line-height: var(--text-h1-line-height);
margin-block: var(--space-l) var(--space-s);
}
h2 {
font-size: var(--text-h2);
line-height: var(--text-h2-line-height);
margin-block: var(--space-l) var(--space-s);
}
h3 {
font-size: var(--text-h3);
line-height: var(--text-h3-line-height);
margin-block: var(--space-l) var(--space-s);
}
h4 {
font-size: var(--text-h4);
line-height: var(--text-h4-line-height);
margin-block: var(--space-l) var(--space-s);
}
p,
ul,
ol {
margin-block: 0 var(--space-m);
}
small {
font-size: var(--text-small);
line-height: var(--text-small-line-height);
}Turn on JavaScript to play with this demo.
Start at 16px and 680px, close to what you're reading right now. Then drag the width out to 1200px and watch the line height grow with it. Wide lines need more room between them, and the math knows it.
How does it work? Everything below is the long version: rems first, then the golden ratio, the line height formula, and fluid type.
What a rem is
A rem is a "root em": the font size of the root element, which is the <html> tag. Every browser sets that to 16px unless the reader changes it. So 1rem is 16px, 2rem is 32px, and 0.5rem is 8px.
An em is the same idea, measured from the parent instead of the root. That sounds harmless until you nest things:
li {
font-size: 1.2em; /* 20% bigger than the parent */
}A list inside a list inside a list is now 1.2 × 1.2 × 1.2 = 1.728 times the body size. Every level compounds. A rem never does: 1.2rem is 19.2px no matter how deep it sits.
How do I convert rem to px?
Multiply the rem value by the root font size. With the browser's default 16px root, 1.5rem is 24px. Drag the root size and watch the whole table move:
| rem | px at a 16px root |
|---|---|
0.5rem | 8px |
0.75rem | 12px |
1rem | 16px |
1.25rem | 20px |
1.5rem | 24px |
2rem | 32px |
2.5rem | 40px |
3rem | 48px |
html { font-size: 100% } is the browser default, so 1rem is 16px and nothing needs dividing by hand.
Turn on JavaScript to play with this demo.
That's the whole trick, and also the whole point: change one number at the root and everything sized in rem follows it. If you need the numbers without JavaScript, here they are at a 16px root and at the 10px root you get from 62.5%:
| rem | px (16px root) | px (10px root) |
|---|---|---|
0.5rem | 8px | 5px |
1rem | 16px | 10px |
1.2rem | 19.2px | 12px |
1.5rem | 24px | 15px |
1.6rem | 25.6px | 16px |
1.8rem | 28.8px | 18px |
2rem | 32px | 20px |
2.4rem | 38.4px | 24px |
2.6rem | 41.6px | 26px |
3rem | 48px | 30px |
4.8rem | 76.8px | 48px |
To go the other way, divide the pixel size by the root size: 24px ÷ 16 = 1.5rem.
The 62.5% trick (and why I stopped)
The old version of this post had you write this:
html {
font-size: 62.5%; /* 62.5% of 16px is 10px */
}
body {
font-size: 1.6rem; /* back up to 16px */
}The appeal is obvious. With a 10px root, 2.6rem is 26px. Just move the decimal! I loved it.
Here's what changed my mind: dividing by 16 isn't hard, and I don't do it by hand anymore. I define the sizes once, as custom properties, and never think about them again. Which brings us to the fun part.
Golden ratio typography
The golden ratio is about 1.618. It's the proportion you get when a line is split so that the whole is to the long part as the long part is to the short part, and it shows up in sunflowers, seashells, and a lot of breathless design blog posts.
For type, the idea is simple: pick a body size, then multiply by the ratio once for each step up. Body text at 16px gives an h3 of 25.9px, an h2 of 41.9px, and an h1 of 67.8px. Pick a ratio and see how it feels:
Heading one
Heading two
Heading three
Body text
Small print
Golden ratio (1.618) on a 16px body: h1 is 67.8px, 4.2 times the body.
:root {
--step-3: 4.236rem; /* 67.8px */
--step-2: 2.618rem; /* 41.9px */
--step-1: 1.618rem; /* 25.9px */
--step-0: 1rem; /* 16px */
--step-minus-1: 0.618rem; /* 9.9px */
}Turn on JavaScript to play with this demo.
Did you try the golden ratio first? That h1 is huge. Three steps of 1.618 multiply out to 4.236, so a 16px body gets a 67.8px headline. That's great for a poster and a lot for a blog post on a phone.
The perfect fourth (1.333) and the minor third (1.2) climb gentler slopes, which is why most type scale tools default to one of them. The headings you're reading now step up by less than 1.3.
The CSS under the demo is real. Copy it, and you have a whole scale in rem that respects the reader's root size.
Golden ratio line height
This is the part of golden ratio typography I still use. In 2011, Chris Pearson worked out a formula that picks a line height from the font size and the line width.
The idea: your eye has to jump from the end of one line back to the start of the next. Wider lines need a little more room to land on the right one. Here's the formula, written as JavaScript:
const PHI = 1.618
// The width a line reads best at for this font size
const goldenWidth = (fontSize * PHI) ** 2
// At the golden width, the line height is exactly fontSize × 1.618
const lineHeight = fontSize * (PHI - (1 / (2 * PHI)) * (1 - width / goldenWidth))Drag the width and watch the gaps between lines:
Your eye jumps from the end of one line back to the start of the next. The longer the line, the farther it travels, and the more room it needs between lines to land on the right one. Too much room, and the paragraph falls apart into stripes.
Line height 25.4px (1.586 unitless). The golden width for 16px type is 670.2px.
Turn on JavaScript to play with this demo.
At 16px the golden width is 670.2px, and the line height there is exactly 25.9px (1.618). Squeeze the column to 300px and it drops to about 23.2px. The change is small, a pixel or two, but you can see the narrow column tighten up. That's the formula doing what a good typographer does by eye.
This site's text column is 680px wide, with body text around 17px. The formula says 1.587. I use 1.7, because I like a little more air than the math does.
That's the formula the calculator at the top runs, once for the body and once for every heading. If the line height it gives you comes out under 1.5, your column is too narrow for your font size, and its best size tells you how far to come down.
Fluid type with clamp()
A fixed scale has one problem left: a headline that looks great on a laptop takes four lines on a phone. clamp() fixes that. It takes a minimum, a preferred value, and a maximum, and the preferred value can grow with the screen width (vw).
Shrink the screen and compare the two headlines:
Golden ratio typography
Fluid clamp()Golden ratio typography
On a 390px screen, the fixed headline is 56px and the fluid one is 33.5px.
h1 {
font-size: clamp(2rem, 1.571rem + 2.143vw, 3.5rem);
}Turn on JavaScript to play with this demo.
At 390px, about the width of a phone, the fixed 3.5rem headline stacks about a word per line. The fluid one is 33.5px and reads like a headline again. Drag all the way up and they meet at 56px, because clamp() stops growing at the maximum.
The math behind that middle value is a straight line between two points (32px on a 320px screen, 56px on a 1440px one). Utopia will do it for you. This site's body text is clamp(1rem, 0.95rem + 0.25vw, 1.15rem), from tokens.css.
rem or em for spacing?
Both, for different jobs.
Use rem for layout: margins between sections, gaps in a grid, padding on a card. Those should follow the reader's font size, but not the size of the text inside them.
Use em for things that belong to their own text. A button's padding should grow when the button's text grows, and an icon next to a heading should match the heading:
.button {
font-size: 1.125rem;
padding: 0.5em 1em; /* scales with this button's text */
}
.card {
margin-block: 2rem; /* scales with the root, not the card's text */
}Putting it all together
Here's the stylesheet I'd write today. No 62.5%, sizes in rem against the reader's own root, a golden ratio line height, and one fluid headline:
:root {
--step-0: 1rem; /* 16px */
--step-1: 1.333rem; /* 21.3px */
--step-2: 1.778rem; /* 28.4px */
--step-3: clamp(2rem, 1.571rem + 2.143vw, 3.5rem);
--line-height: 1.62; /* the golden ratio formula at 16px and 680px */
}
body {
font-size: var(--step-0);
line-height: var(--line-height);
}
h1 {
font-size: var(--step-3);
line-height: 1.1;
}
h2 {
font-size: var(--step-2);
}Every value comes from the reader's root, so if they set their browser to 20px, the whole page grows with it. Custom properties work in every modern browser, and you can tweak them live in DevTools.
What I'd tell 2013 me
- Leave the root alone.
16pxis the reader's choice, and dividing by 16 is fine. - Use rem for sizes and layout. It never compounds, so you always know what you'll get.
- Try the golden ratio, then try 1.25. The golden ratio is a great line height and a steep type scale.
- Make headlines fluid. One
clamp()on the biggest sizes fixes most of the phone problems. - Use em for padding that belongs to its text. Buttons and badges grow with their own label.
Where to read more
- CSS values and units on MDN, for rem, em, and everything else
- The surprising truth about pixels and accessibility by Josh Comeau, on when to use px, rem, and em
- Utopia, a fluid type and space calculator
- Font sizing with rem by Jonathan Snook, from 2011, one of the posts I learned rem from
R.E.M. is still a band. These days, it's also the first unit I reach for.