Skip to content
Greg Rickaby: Alabama-based software engineer, self-taught photographer, and published author.

Updated 8 min readCode

Golden ratio typography with rem units

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.

Preview
StyleSizeLine height
h12.618rem 41.9px1.355 56.7px
h22.058rem 32.9px1.383 45.5px
h31.618rem 25.9px1.429 37px
h41.272rem 20.4px1.503 30.6px
body1rem 16px1.623 26px
small0.786rem 12.6px1.816 22.8px
space-xs 9.9px
space-s 16px
space-m 26px
space-l 42px
space-xl 68px
: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:

rempx at a 16px root
0.5rem8px
0.75rem12px
1rem16px
1.25rem20px
1.5rem24px
2rem32px
2.5rem40px
3rem48px
Root

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%:

rempx (16px root)px (10px root)
0.5rem8px5px
1rem16px10px
1.2rem19.2px12px
1.5rem24px15px
1.6rem25.6px16px
1.8rem28.8px18px
2rem32px20px
2.4rem38.4px24px
2.6rem41.6px26px
3rem48px30px
4.8rem76.8px48px

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:

h1 67.8px

Heading one

h2 41.9px

Heading two

h3 25.9px

Heading three

body 16px

Body text

small 9.9px

Small print

Ratio

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:

Fixed 3.5rem

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

  1. Leave the root alone. 16px is the reader's choice, and dividing by 16 is fine.
  2. Use rem for sizes and layout. It never compounds, so you always know what you'll get.
  3. Try the golden ratio, then try 1.25. The golden ratio is a great line height and a steep type scale.
  4. Make headlines fluid. One clamp() on the biggest sizes fixes most of the phone problems.
  5. Use em for padding that belongs to its text. Buttons and badges grow with their own label.

Where to read more

R.E.M. is still a band. These days, it's also the first unit I reach for.