How to Tame Line Height in CSS
The line-height
attribute in CSS is probably one of the most misunderstood but most commonly used attributes. As designers and developers, when we think of line-height
, we may think of the concept of "leading" in print designs—interestingly, this term actually comes from placing lead blocks between typesetting rows.
Although the line spacing and line-height
are similar, there are some important differences. To understand these differences, we first need to learn more about typesetting.
Overview of typesetting terms
In traditional Western font design, a line of text consists of several parts:
- Baseline: This is the imaginary line where the text is located. When you write on a gridded notebook, the baseline is the line where you write.
- Descender: This line is located below the baseline. The bottom of some characters (such as lowercase letters g, j, q, y, and p) will touch this line.
- x-height: This is the height of the normal lowercase letter x in the text. Typically, this is the height of other lowercase letters, although some letters may have portions above the x height. For all intents and purposes, it represents the perceived height of lowercase letters.
- Cap-height: This is the height of most capital letters in a given line of text.
- Ascender: A line that often appears above the capital height, some characters (such as lowercase letter h or b) may exceed the normal capital height.
Each part of the text described above is inherent in the font itself. These parts are taken into account when designing fonts; however, there are some parts of the typography that are decided by the typist (like you and me!) rather than the designer. One of them is the lead.
Leading is defined as the distance between two baselines in a set of fonts.
CSS developers might think, "OK, line spacing is line-height
, let's go ahead." Although the two are related, there are differences in some very important aspects.
Let's create a blank document and add a classic "CSS reset" to it:
* { margin: 0; padding: 0; }
This will remove the margins and inner margins of each element.
We will also use Lato from Google Fonts as our font family.
We need some content, so let's create one<p></p>
Tags that contain some text and set line-height
to a very large value, such as 300px. The result is a line of text, with surprisingly large amount of space above and below that line of text.
When the browser encounters the line-height
property, all it actually does is take a line of text and place it in the middle of a "line box" whose height matches the element's line-height
. Instead of setting the line spacing of the font, we get an effect similar to adding padding on both sides of the line box.
As shown above, the line frame surrounds a line of text, and the line spacing is created by using space below and above the next line of text. This means that for each text element on the page, in a specific text block, there will be half the line spacing above the first line and below the last line of text.
What's even more surprising is that explicitly setting line-height
and font-size
of the same value on one element leaves extra space above and below the text. We can see this by adding background color to the element.
This is because even if font-size
is set to 32px, the actual text size is smaller than that value due to the generated spacing.
Let CSS handle line-height like line spacing
If we want CSS to use a more traditional typography style instead of a line box, we want single-line text to have no space above or below it - but allow multi-line elements to keep their full line-height
value.
With a little effort, CSS can understand line spacing. Michael Taranto has solved this problem by releasing a tool called Basekick. It does this by applying a negative margin to the ::before
pseudo-element and applying translateY
to the element itself. The end result is that there is no extra space around a line of text.
The latest version of the Basekick formula can be found in the source code of the Braid Design System in SEEK. In the example below, we are writing a Sass mixin to do the heavy lifting, but the same formula can also be used with JavaScript, Less, PostCSS mixin, or any other tool that provides such math capabilities.
@function calculateTypeOffset($lh, $fontSize, $descenderHeightScale) { $lineHeightScale: $lh / $fontSize; @return ($lineHeightScale - 1) / 2 $descenderHeightScale; } @mixin basekick($typeSizeModifier, $baseFontSize, $descenderHeightScale, $typeRowSpan, $gridRowHeight, $capHeight) { $fontSize: $typeSizeModifier * $baseFontSize; $lineHeight: $typeRowSpan * $gridRowHeight; $typeOffset: calculateTypeOffset($lineHeight, $fontSize, $descenderHeightScale); $topSpace: $lineHeight - $capHeight * $fontSize; $heightCorrection: 0; @if $topSpace > $gridRowHeight { $heightCorrection: $topSpace - ($topSpace % $gridRowHeight); } $preventCollapse: 1; font-size: #{$fontSize}px; line-height: #{$lineHeight}px; transform: translateY(#{$typeOffset}em); padding-top: $preventCollapse; &::before { content: ""; margin-top: #{-($heightCorrection $preventCollapse)}px; display: block; height: 0; } }
At first glance, this code must look like a lot of magic numbers pieced together. However, by considering it in the context of a particular system, it can be broken down. Let's take a look at what we need to know:
-
$baseFontSize
: This is the normal font size around which everything else in our system will manage. We will use 16px as the default value. -
$typeSizeModifier
: This is a multiplier used with the benchmark font size to determine the font size rules. For example, a value of 2 is used in conjunction with our benchmark font size 16px, which will give usfont-size: 32px
. -
$descenderHeightScale
: This is the height of the font sinking line expressed in ratio. For Lato, this seems to be about 0.11. -
$capHeight
: This is the specific capital height of the font expressed in ratio. For Lato, this is about 0.75. -
$gridRowHeight
: Layouts usually rely on the default vertical rhythm to create a good and consistent reading experience. For example, all elements on a page may be spaced in multiples of 4 or 5 pixels. We will use 4 as the value because it can be easily divided by our$baseFontSize
of 16px. -
$typeRowSpan
: Similar to$typeSizeModifier
, this variable is used as a multiplier and is used with grid row height to determine the line height value of the rule. If our default grid line height is 4 and our type line span is 8, we getline-height: 32px
.
Now we can substitute these numbers into the Basekick formula above (with the help of SCSS functions and mixin), which will give us the following results.
This is exactly what we want. For any set of text block elements without margins, these two elements should be close to each other. This way, any margins set between the two elements will be pixel-perfect , as they will not conflict with the line-frame spacing.
Optimize code
Rather than putting all your code into a single SCSS mixin, organize it better. If we think from a system perspective, we will notice that we are dealing with three types of variables:
Considering these aspects will help us expand our system more easily. Let's view each group in turn.
First, system-level variables can be set globally, as these variables are unlikely to change during the project process. This reduces the number of variables in our main mixin to four:
$baseFontSize: 16; $gridRowHeight: 4; @mixin basekick($typeSizeModifier, $typeRowSpan, $descenderHeightScale, $capHeight) { /* Same as above */ }
We also know that font-level variables are specific to their given font family. This means it's easy to create higher order mixins to set them to constants:
@mixin Lato($typeSizeModifier, $typeRowSpan) { $latoDescenderHeightScale: 0.11; $latoCapHeight: 0.75; @include basekick($typeSizeModifier, $typeRowSpan, $latoDescenderHeightScale, $latoCapHeight); font-family: Lato; }
Now, based on the rules, we can easily call Lato mixin:
.heading--medium { @include Lato(2, 10); }
This output gives us the rule to use Lato fonts with a font size of 32px, a line height of 40px, and all related conversions and margins. This allows us to write simple style rules and take advantage of the grid consistency that designers are accustomed to when using tools like Sketch and Figma.
As a result, we can easily create pixel-perfect web designs. Please see the example below how to align well with our base 4px grid. (You may need to zoom in to see the grid.)
Doing so gives us a unique ability when creating website layouts: for the first time ever we can really create pixel-perfect pages. Combining this technique with some basic layout components, we can start creating pages like we do in design tools.
Moving towards standardization
While it takes some effort to make CSS behave more like our design tools, there may be good news in the future. It has been proposed to add an additional feature to the CSS specification to switch this behavior locally. The current state of the proposal is that an additional property similar to line-height-trim
or leading-trim
will be added to the text element.
One of the amazing things about online languages is that we all have the ability to participate. If this looks like a feature you want to be part of your CSS, you can participate and add comments to the topic so that your voice is heard.
The above is the detailed content of How to Tame Line Height in CSS. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics



If you’ve recently started working with GraphQL, or reviewed its pros and cons, you’ve no doubt heard things like “GraphQL doesn’t support caching” or

With the recent climb of Bitcoin’s price over 20k $USD, and to it recently breaking 30k, I thought it’s worth taking a deep dive back into creating Ethereum

It's out! Congrats to the Vue team for getting it done, I know it was a massive effort and a long time coming. All new docs, as well.

No matter what stage you’re at as a developer, the tasks we complete—whether big or small—make a huge impact in our personal and professional growth.

I had someone write in with this very legit question. Lea just blogged about how you can get valid CSS properties themselves from the browser. That's like this.

I'd say "website" fits better than "mobile app" but I like this framing from Max Lynch:

The other day, I spotted this particularly lovely bit from Corey Ginnivan’s website where a collection of cards stack on top of one another as you scroll.

There are a number of these desktop apps where the goal is showing your site at different dimensions all at the same time. So you can, for example, be writing
