Within CSS, line-height is probably one of the most confusing, yet commonly-used attributes. As creative designers and developers, when we think about line-height, we might think about the concept of leading through print design — a expression, interestingly enough, that comes from literally placing pieces of lead between lines associated with type. Leading and line-height, nevertheless similar, have some important differences. To comprehend those differences, we first need to understand a bit more about typography.
A summary of typography terms
In developed type design, a line of textual content is comprised of several parts:
Primary: This is the imaginary line on which the kind sits. When you write in a dominated notebook, the baseline is the range on which you write. Descender: This particular line sits just below the primary. It is the line that some personas — like lowercase g, m, q, y and p — touch below the baseline. X-height: This is (unsurprisingly) the height of the normal, lowercase x in a type of text. Generally, this is the height associated with other lowercase letters, although some might have parts of their characters that will surpass the x-height. For all intents and purposes, it machines as the perceived height of lowercase letters. Cap-height: This is the height on most capital letters on a given type of text. Ascender: A line that will oftentimes appears just above the particular cap height where some personas like a lowercase h or w might exceed the normal cap elevation. Each of the parts of text described over are intrinsic to the font alone. A font is designed with each one of these parts in mind; however , there are some areas of typography that are left up to the kind setter (like you and me! ) as opposed to the designer. One of these is leading.
Top is defined as the distance between two baselines in a set of type.
A CSS developer might think, “OK, top is the line-height, let’s move on. ” While the two are related, also, they are different in some very important ways.
Let us take a blank document and put in a classic “CSS reset” to it:
2.
  margin: 0;
  padding: 0;

This removes the perimeter and padding from every single component.
We’ll also use Lato through Google Fonts as our font-family.
We will need some content, therefore let’s an create an < h1> tag with some text make the line-height to something obnoxiously huge, like 300px. The result is really a single line of text with a amazing amount of space both above plus below the single line of textual content.
When a browser encounters the line-height property, what it actually does will be take the line of text and place this in the middle of a “line box” with a height matching the element’s line-height. Instead of setting the leading on a typeface, we get something akin to padding one particular either side of the line package.
As illustrated above, the line container wraps around a line of text exactly where leading is created by using space beneath one line of text and over the next. This means that for every text component on a page there will be half of the best above the first line of text after the last line of text in a specific text block.
What might be a lot more surprising is that explicitly setting the particular line-height and font-size on an component with the same value will depart extra room above and beneath the text. We can see this by adding the background color to our elements.
It is because even though the font-size is set to 32px, the actual text size is something lower than that value because of the generated space.
Getting CSS to treat line-height such as leading
If we want CSS to use a classical type setting style instead of the series box, we’ll want a single type of text to have no space possibly above or below it — but allow for multi-line elements to keep their entire line-height value.
You are able to teach CSS about leading after some bit of effort. Michael Taranto launched a tool called Basekick that resolves this very issue. It does therefore by applying a negative top margin towards the:: before pseudo-elementand a translateY towards the element itself. The end result is a type of text without any extra space about it.
The most up-to-date version associated with Basekick’s formula can be found in the source program code for the Braid Design System through SEEK. In the example below, we have been writing a Sass mixin to accomplish the heavy lifting for us, however the same formula can be used with JavaScript, Less, PostCSS mixins, or anything that provides these kinds of math features.
@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: #$fontSizepx;
  line-height: #$lineHeightpx;
  transform: translateY(#$typeOffsetem);
  padding-top: $preventCollapse;

  &::before
    content: “”;
    margin-top: #-($heightCorrection + $preventCollapse)px;
    display: block;
    height: 0;
 

At first glance, this program code definitely looks like a lot of magic amounts cobbled together. But it can be separated considerably by thinking of it within the context of a particular system. Let us take a look at what we need to know:
$baseFontSize: This is actually the normal font-size for our system about which everything else will be managed. We will use 16px as the default worth. $typeSizeModifier: This is a multiplier that is used with the base font size to determine the font-size rule. For example , a value of two coupled with our base font dimension of 16px will give us font-size: 32px. $descenderHeightScale: This is the height from the font’s descender expressed as a percentage. For Lato, this seems to be close to 0. 11. $capHeight: This is the font’s specific cap height expressed as being a ratio. For Lato, this is about 0. 75. $gridRowHeight: Layouts usually rely on default a vertical tempo to make a nice and consistently spaced reading through experience. For example , all elements on the page might be spaced apart within multiples of four or five pixels. We will be using 4 as the value since it divides easily into our $baseFontSize of 16px. $typeRowSpan: Like $typeSizeModifier, this variable serves as a multiplier to be used with the grid row elevation to determine the rule’s line-height value. In the event that our default grid row elevation is 4 and our kind row span is 8, that will leave us with line-height: 32px. Today we can then plug those figures into the Basekick formula above (with the help of SCSS functions and mixins) and that will give us the result beneath.
That’s just what we’re looking for. For every set of text block elements with out margins, the two elements should bundle against each other. This way, any margins set between the two elements is going to be pixel perfect because they won’t end up being fighting with the line box space.
Refining our code
Instead of throwing all of our code into a single SCSS mixin, let’s organize it a bit much better. If we’re thinking in terms of techniques, will notice that there are three varieties of variables we are working with:
Variable TypeDescriptionMixin VariablesSystem LevelThese values are qualities of the design system we’re dealing with. $baseFontSize
$gridRowHeightFont LevelThese values are intrinsic to the typeface we’re using. There might be some speculating and tweaking involved to get the ideal numbers. $descenderHeightScale
$capHeightRule LevelThese values will are particular to the CSS rule we’re creating$typeSizeMultiplier
$typeRowSpanThinking in these conditions will help us scale our system easier. Let’s take each group consequently.
First off, the system level variables could be set globally as those are usually unlikely to change during the course of our task. That reduces the number of variables within our main mixin to four:
$baseFontSize: 16;
$gridRowHeight: 4;
@mixin basekick($typeSizeModifier, $typeRowSpan, $descenderHeightScale, $capHeight)
  /* Same as above */

All of us also know that the font degree variables are specific to their provided font family. That means it would be simple enough to create a higher-order mixin that will sets those as constants:
@mixin Lato($typeSizeModifier, $typeRowSpan)
  $latoDescenderHeightScale: 0.11;
  $latoCapHeight: 0.75;
  
  @include basekick($typeSizeModifier, $typeRowSpan, $latoDescenderHeightScale, $latoCapHeight);
  font-family: Lato;

Now, on the rule basis, we can call the particular Lato mixin with little hassle:
. heading–medium
  @include Lato(2, 10);

That output provides us a rule that utilizes the Lato font with a font-size of 32px and a line-height of 40px with all of the relevant translates and margins. This allows us to write simple design rules and utilize the grid persistence that designers are accustomed to when you use tools like Sketch and Figma.
As a result, we can easily create pixel-perfect designs with little fuss. Observe how well the example aligns to the base 4px grid below. (You’ll likely have to zoom in to view the grid. )
Doing this gives all of us a unique superpower when it comes to creating designs on our websites: We can, for the first time of all time, actually create pixel-perfect pages. Few this technique with some basic layout parts and we can begin creating pages in the same manner we would in a design tool.
Relocating toward a standard
While teaching CSS to behave more like our style tools does take a little energy, there is potentially good news on the horizon. A good addition to the CSS specification continues to be proposed to toggle this habits natively. The proposal, as it appears now, would add an additional real estate to text elements similar to line-height-trim or leading-trim.
One of the amazing reasons for web languages is that we all come with an ability to participate. If this seems like an attribute you would like to see as part of CSS, you might have the ability to drop in and put in a comment to that thread to allow your voice be heard.