In this article we are going to take a look at how we can remove this space from the text elements using a simple SCSS mixin.
This mixin was originally created for our library of web components and can be easily included in your web project as it has no external dependencies.
👋pssst...we've created a visual tool to set and export the code for the line-height crop (based on our framework). Check out the Typography Editor.
The mixin
Here’s the mixin you can use to remove the top space from your text element:
@mixin lhCrop($line-height) {
&::before {
content: '';
display: block;
height: 0;
width: 0;
margin-top: calc((1 - #{$line-height}) * 0.5em);
}
}
Include it in the text element you want to modify, making sure to pass the line-height as mixin argument:
.text-to-crop {
@include lhCrop(1.2); //line-height: 1.2
}
In the framework we built for the CodyHouse library of components, the line-height values are expressed as CSS variable; this means we can use those CSS variables to call the lhCrop mixin:
:root {
/* line-height */
--heading-line-height: 1.2;
--body-line-height: 1.4;
--article-line-height: 1.58;
}
.text-to-crop {
@include lhCrop(var(--heading-line-height));
}
One of the advantages of doing so is that the crop value will be automatically updated if you decide to change the value of your CSS variables (with no need to modify the mixin call) or if the line-height variable is modified at different media queries (with no need to re-use the mixin).
The mixin explained
When using the lhCrop mixin, a ::before pseudo-element is created with a negative margin which cuts the space above your text element.
The idea of using a pseudo element with a negative margin was first introduced by Kevin Powell. He also created the Text Crop Tool that allows you to create your custom mixin according to the font family you are using in your project.
For our mixin, here’s how we got the value for the margin-top:
- The total height of the text element (capital letter height + top space + bottom space) is equal to the line-height multiplied by the font-size (if you are using a pure number for your line-height);
- We assume the height of the capital letter is equal to the font-size (this is an approximation, more about that in the Disclaimer section);
This means the sum of top space and bottom space is going to be:
total-space = font-size*line-height - font-size = (line-height - 1)*font-size = (line-height - 1)*1em
Note we have been using 1em as font-size; this is because the font-size of a ::before pseudo-element is, by default, equal to the font-size of the element it is attached to.
Assuming bottom and top spaces are equal, it gives you:
top-space = (line-height - 1)*1em/2 = (line-height - 1)*0.5em
The opposite of this top-space value gives you the value used in the mixin (you want a negative margin to crop the top space).
We could have created a similar mixin for the bottom space (or included it in the lhCrop mixin) but this is something we have not been needing so far (so we probably won’t be including it).
If this is something you need, then here’s what you have to do:
- create an ::after pseudo-element (this time you want to remove the space from the bottom of the text);
- add a negative margin-bottom (you can use the same value used as margin-top in the lhCrop mixin).
Disclaimer
The lhCrop mixin is based on two simplifications:
- The capital letter height is equal to font-size; this is usually not true. If you consider the Roboto font family, for example, the capital letter is about 75% of the font size;
- The space on top of the capital letter is equal to the space at the bottom; again, this is usually not true.
In our experiments, it turned out these two simplifications were more than acceptable and we were always able to obtain the result we were looking for. You may not be able to achieve a pixel-perfect crop but, in our opinion, the ease of use of the mixin makes up for this minor approximation.
You can push the mixin a little further if you want to address the first simplification.
Taking the Roboto font family as example, if the height of the capital letter is not 1*font-size but 0.75*font-size, then the above margin value becomes:
margin-top: (0.75 - line-height)*0.5em
Our lhCrop mixin could be modified to take this variable into account:
@mixin lhCrop($line-height, $capital-letter: 1) {
&::before {
content: '';
display: block;
height: 0;
width: 0;
margin-top: calc((#{$capital-letter} - #{$line-height}) * 0.5em);
}
}
This time we can pass a second value to the mixin: by default, this value is 1 (so capital-letter height equal to font-size) but you could use a different value if you need more control. In this case, here’s how you would use the mixin:
.text-to-crop {
@include lhCrop(1.2, 0.75); //using Roboto font family
}
If you are planning on using this modified version of the mixin, then a good approach would be to define, in your custom-style/_typography.scss file, a -- capital-letter variable for each one of your fonts and use that variable when calling the lhCrop mixin:
:root {
/* line-height */
--heading-line-height: 1.2;
--body-line-height: 1.4;
--article-line-height: 1.58;
/* capital letters - used in combo with the lhCrop mixin */
--font-primary-capital-letter: 0.75;
--font-secondary-capital-letter: 0.69;
}
.text-to-crop {
@include lhCrop(var(--body-line-height), var(--font-primary-capital-letter));
}
This makes your system quite easy to maintain: if you need to change your font-family, you’ll only need to update the -- capital-letter variable with no need to modify the mixin calls.
Here’s an example of this mixin in action:
As for the second simplification, you would need to take into account the descender (bottom space) and the ascender (top space + capital letter) of the font family you are using.
We actually didn’t go into this level of customization as it was not necessary for our use-cases.
If you feel like you need a pixel-perfect precision or if the font you are using has a strong unbalance between top and bottom spaces, then the Text Crop Tool is probably a better solution for you.
For now, we have decided to go with the simplified version of the mixin (we have not included the second $capital-letter argument) as it turned out to be accurate enough in all our experiments so far.