DEV Community

Cover image for Paragraph styling based on x-height (and how to implement it with CSS)
Masa Kudamatsu
Masa Kudamatsu

Posted on

Paragraph styling based on x-height (and how to implement it with CSS)

TL;DR

I'll explain how to style the paragraphs of text on a webpage like this:

(Screenshot of my CodePen demo)

If you feel the above screenshot somehow appears more elegant than ordinary webpages, read on.

Introduction

I believe the paragraphs of text appear beautiful when their vertical spacing is a multiple of the x-height of text.

The line-height value should be, say, 3 times as high as x-height. Human eyes will then easily recognize that the whitespace between a pair of lines doubles the size of each line (as shown in the top image).

Likewise, the space between paragraphs should be, say, 4 times as high as x-height. Human eyes will then easily recognize that the whitespace between paragraphs doubles the size of the one between lines (as shown in the image below).

Achieving this beautiful typography was difficult for webpages until very recently. Thanks to a new CSS property called text-box, it is now easier to implement. This article shows you how.

1. Setup

Font metrics

We first set the font-family name and its ratio of x-height to font-size:

:root {
  /* Font metric */
  --body-font: "Cormorant Garamond";
  --x-height-to-font-size-ratio: 0.385; /* Value obtained from Section 4 */
Enter fullscreen mode Exit fullscreen mode

In Section 4, I’ll explain how to obtain the ratio of x-height to font-size for a font of your choice.

Design tokens

Next, we create design tokens for spacing:

:root {
  --space100: 0.625rem; /* Change this value to your taste */
  --space200: calc(var(--space100) * 2);
  --space300: calc(var(--space100) * 3);  
  --space400: calc(var(--space100) * 4);  
  --space500: calc(var(--space100) * 5);  
}
Enter fullscreen mode Exit fullscreen mode

If you like, you can change the scaling factor:

:root {
  --space100: 0.625rem; /* Change this value to your taste */
  --space200: calc(var(--space100) * 3/2); /* x1.5 */
  --space300: calc(var(--space100) * 9/4); /* x1.5x1.5 */
  --space400: calc(var(--space100) * 27/8); /* x1.5x1.5x1.5 */  
  --space500: calc(var(--space100) * 81/16); /* x1.5x1.5x1.5x1.5 */  
}
Enter fullscreen mode Exit fullscreen mode

Spacing hierarchy

Finally, specify x-height and spaces between lines and between paragraphs:

:root {
  --x-height: var(--space100);
  --btw-lines: var(--space200); 
  --btw-paragraphs: var(--space400);
}
Enter fullscreen mode Exit fullscreen mode

By "space between lines", I mean the whitespace below the baseline of text and above the top of the lowercase x in next line:

If you prefer a tighter gap between paragraphs, try the following instead:

:root {
  --x-height: var(--space100);
  --btw-lines: var(--space200);
  --btw-paragraphs: var(--space300);
}
Enter fullscreen mode Exit fullscreen mode

Some web developers advocate what's known as "vertical rhythm". In this methodology, you insert one empty line between paragraphs so that the baseline of text always sits on an equally-distanced horizontal lines across the page. To implement it, we need to have:

:root {
  --x-height: var(--space100);
  --btw-lines: var(--space200);
  --btw-paragraphs: var(--space500);
}
Enter fullscreen mode Exit fullscreen mode

In next section, I'll introduce the rest of CSS rules that act like a computer program to render text and spacing. Changing the parameters above will automatically alter how text appears.

2. font-size and line-height

Set font properties with the CSS variables created in the previous section as follows:

p {
  font-family: var(--body-font);
  font-size: calc(var(--x-height) / var(--x-height-to-font-size-ratio));
  line-height: calc(var(--x-height) + var(--btw-lines));
}
Enter fullscreen mode Exit fullscreen mode

Multiplying font-size with the x-height to font-size ratio yields x-height. Consequently, font-size can be derived by dividing x-height with the x-height to font-size ratio.

The line-height value corresponds to the vertical distance from the baseline of text to the next line's baseline of text, which is the sum of the space between lines and x-height.

The above will render a paragraph of text beautifully.

3. Space between paragraphs

One of the core principles of UI design is Gestalt psychology (link to my blog post). Related pieces of information should be placed in proximity while unrelated ones should be placed far from each other. This way, informational structure is conveyed visually at one glance.

Letters within a paragraph are related pieces of information, but they are less related to those within the previous/next paragraphs.

Consequently, the space between paragraphs should be larger than the space between lines of text within a paragraph.

With CSS, we can achieve this in two ways: one for cross-browser support and the other for more readable css (with a fallback for Firefox).

Cross-browser support approach

This approach is simple in terms of the number of CSS rules, but it is rather difficult to interpret:

  p + p {
    margin-top: calc(var(--btw-paragraphs) - var(--btw-lines));
  }
Enter fullscreen mode Exit fullscreen mode

Without any vertical margins of two consecutive paragraph elements, the last line of the paragraph above and the first line of the paragraph below are separated by the whitespace between lines. Consequently, the margin-top value of the paragraph below has to be the difference in height between the spaces between paragraphs and between lines.

If this sounds complicated, go for the next approach.

More readable CSS approach

With a new CSS property text-box (supported by Chrome and Safari as of June 2026), we can achieve the same height of the whitespace between paragraphs with the following CSS code:

p {
  text-box: trim-both ex alphabetic;  
}
p + p {
  margin-top: var(--btw-paragraphs);
}
/* Firefox fallback */
@supports not (text-box: trim-both ex alphabetic) {
  p + p {
    margin-top: calc(var(--btw-paragraphs) - var(--btw-lines));
  }
}
Enter fullscreen mode Exit fullscreen mode

The CSS declaration text-box: trim-both ex alphabetic trims the whitespace above the flat top of the lowercase x in the first line of text and the whitespace below the baseline in the last line of text.

Screenshot of "CSS text-box-trim animation" by Adam Argyle in November 2024. (Don't see this link with Firefox, which does not support text-box.)

That allows us to set the margin-top of the following paragraph to be exactly the space between a pair of paragraphs.

However, as of June 2026, Firefox does not support text-box. To provide a fallback for Firefox, we need to use the cross-browser support approach in the @supports query.

4. Obtaining the exact ratio of x-height to font-size

Now let me explain how to obtain the ratio of x-height to font-size, the most important parameter of the entire exercise discussed so far.

With the adoption of a new CSS property text-box by Safari and Chrome, the ratio of x-height to font-size is now easy to obtain with the following simple HTML/CSS code:

HTML

Assuming we use "Cormorant Garamond" from Google Fonts:

<head>
  <!-- Embedded code from Google Fonts -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300..700;1,300..700&display=swap" rel="stylesheet">
</head>
<body>
  <div></div>
  <p>
    After six months, I couldn’t see the value in it. I had no idea what I wanted to do with my life, and no idea how college was going to help me figure it out. And here I was, spending all of the money my parents had saved their entire life. — Steve Jobs (Stanford University Commencement Address on June 12, 2005)
  </p>
</body>
Enter fullscreen mode Exit fullscreen mode

The <div> element will be used to render a ruled paper background. The <p> element can be any paragraph, but make sure the first line includes the lowercase x.

The lowercase x is a great character because it has a flat top as well as a flat bottom sitting on the baseline. It allows us to easily measure the height of it.

CSS

:root {
  --body-font: "Cormorant Garamond"; /* replace this with your font */
  --x-height-to-font-size-ratio: 0.385; /* tweak this value */
}
body {
  --x-height: 100px;
  --btw-lines: 100px;
}
p {
  font-family: var(--body-font);
  font-size: calc(var(--x-height) / var(--x-height-to-font-size-ratio));
  line-height: calc(var(--x-height) + var(--btw-lines));
  text-box: trim-both ex alphabetic;
}
/* Ruled page background */
div {
  /* Draw 1px grey lines at 100px intervals */
  background: repeating-linear-gradient(
    to bottom,
    #aaa 0px,
    #aaa 1px,
    transparent 1px,
    transparent 100px
  );
  /* Render behind the paragraph element */
  height: 1000px; /* up to 5 lines of text */
  position: absolute;
  width: 100%;
  z-index: -1;
}
Enter fullscreen mode Exit fullscreen mode

I have prepared a CodePen demo. You can fork it for your use: https://codepen.io/masakudamatsu/pen/gbgxRdd

Then start with:

  --x-height-to-font-size-ratio: 0.5;
Enter fullscreen mode Exit fullscreen mode

If the lowercase x is taller than 100px, increase the value. If it's shorter than 100px, *decrease the value.

Make sure that the flat top of the lowercase x overlaps the first 1px grey line while there is no whitespace between the flat bottom of the lowercase x and the second 1px grey line. If so, that means the x-height is 100px.

Three decimal digits are enough. Doing this exercise for Cormorant Garamond took me to the value of 0.385. I did the same exercise for Verdana, a system font of most OS's (except Android), which yielded the value of 0.546.

I've found this value can be different from the "ex-height" value stored in font files. Web tools such as Font metrics calculator for font-size-adjust allow you to upload a font file and extract the ratio of x-height to font-size stored in it. When I uploaded the font file of Cormorant Garamond, downloaded from Google Fonts, I got the value of 0.388. But this value gave me the x-height of 101px.

In passing, classic fonts (or those inspired by classic fonts, like Cormorant Garamond) typically have the value less than 0.5. Modern fonts like Verdana have the value larger than 0.5, for the readability of small-sized text on webpages. A large x-height has become the font design trend since the 1970s (such as Avant Garde Gothic and ITC Garamond).

Top comments (0)