CSS supplies us with root and relative values.
- Root values are like
rem
andrlh
— they’re tied to the values written within the:root
selector (the most typical one can be thehtml
aspect). - Relative values are like
em
,lh
,ch
and varied others — they’re tied to thefont-size
in that particular aspect.
I’ve come to comprehend that maybe we have to have a unit between root and relative values. Having such a unit permits us to measurement issues with out complicated em
or lh
calculations.
Let me provide you with an instance: Prose
Earlier this yr, Jen Simmons wrote about the utilizing the lh
unit to type margin
and padding
for higher typographical vertical rhythm.
p { margin-block: 1lh; }
We are able to develop the idea a little bit additional to incorporate all different areas across the textual content. A method of doing that is the “Lobotomized Owl” method that Heydon Pickering popularized some time in the past.
* + * {
margin-top: 1lh;
}
In the present day, we are able to additionally use the :not(:first-child)
to realize the identical impact — and that may be a tad extra readable.
*:not(:first-child) {
margin-top: 1lh;
}
Usually, we have to constrain these selectors in order that they don’t spill all over the place and break the remainder of the web page. One nice class for that is .prose
.
.prose {
*:not(:first-child) {
margin-top: 1lh;
}
}
That is easy and good — however what occurs for those who embrace typography of different sizes…? You’ll see this break down extremely rapidly (as a result of 1lh
of a
A method round this concern is to make use of Flexbox on the dad or mum aspect. By doing so, we are able to set hole
to 1lh
and we don’t need to take care of the worth of 1lh
altering on the h2
aspect. (Bonus, we additionally don’t need to take care of margin collapse.)
.prose {
show: flex;
flex-direction: column;
hole: 1lh;
}
However we introduce a brand new drawback right here: proximity confusion.
Content material under
. However content material above the
belongs with the earlier part header. We should always, ideally, make the spacing completely different to make clear their relationship.
The best approach is so as to add a little bit margin above the
However we are able to’t add margin above
lh
because the lh
worth on
shall be completely different from that of the encircling components.
We have now to make use of a little bit CSS trickery and margin-bottom
(or the logical equal) on the aspect above the
margin-bottom
to 1lh
since we use Flexbox and don’t need to take care of margin collapse. (In case you needed to take care of margin collapse, you’d need to set margin-bottom
to 2lh
.)
Is there a greater approach? Nicely, that’s what this text is about!
However earlier than we go there, let’s think about a special UI that has comparable issues so you possibly can start to see the better ramifications of this drawback (and the significance of the answer).
Right here’s a second instance: Card part
Now let’s say we now have a card part that’s divided into two components, header and content material.
In these type of parts, the header is commonly styled with a special font-size
than the content material.

To create such a card, the best markup could also be:
Sadly, we can't use the lh
unit to create the padding inside the card — doing so causes the margin on the
There are, in fact, some ways to deal with the sort of state of affairs.
One potential approach is to vary the markup such that the
aspect. Once we do that, we are able to apply the padding on the
, bypassing the enlarged 1lh
drawback.
Whereas altering the markup solves the issue, it’s not superb — since we in all probability don’t wish to create an additional header
aspect until it’s obligatory…
Nicely, one other potential technique is to make use of a root worth like rlh
. This enables
content material
to make use of the identical base unit, and due to this fact, create the identical padding.
However we nonetheless run into issues if the .card
must scale to completely different font-size
values. Think about you wish to make a smaller card — now 1rlh
isn’t going to look proper because the padding worth turns into too massive in proportion to the content material.
What can we do?
A easy answer is to vary the padding
worth in accordance with the supported variants of the part — however this kinda factor is sorta hard-coded and never very pleasant…
.card-sm { --padding: 0.75rlh; }
.card-md { --padding: 1rlh; }
.card-lg { --padding: 1.25rlh; }
What’s the choice?
That is the place an middleman between root and relative models may come in useful.
The useful in-between unit
This part is only speculative CSS as an instance a degree. We’ll comply with up with a easy method to really do that in follow at the moment in a later part, so dangle tight and comply with alongside conceptually for now.
Let’s say we now have a unit that takes it’s reference worth from a specified aspect. We’ll name this a base
unit, for lack of a greater identify.
- So, 1
base
font-size unit could possibly be1bem
. - And 1
base
line-height unit could possibly be1blh
.
Fairly straightforward at this level.
Think about we are able to type the playing cards with this base unit. Then we are able to merely use 1blh
to quantify the padding and every part else can be sized appropriately:
.card {
> * { padding: 1blh; }
}
.card-sm { font-size: 0.8em; }
.card-md { font-size: 1em; }
.card-lg { font-size: 1.2em; }
Hurrah?
Tying this again to the .prose
instance earlier, it might very effectively resolve the proximity confusion concern with out complicating our selectors:
.prose {
h2:not(:first-child) {
margin-top: 2blh;
}
}
How may this work?
For this operate to be added simply to trendy CSS, I might consider two potential methods:
- Connect that to container queries.
- Outline a syntax much like anchor positioning.
The container question technique
We have already got stuff like cqw
and cqh
to indicate container width and container peak values. It’s not too far of a cry to say we might have a cqem
(container-query em
) unit or cqlh
(container-query line-height).
There are downsides to this strategy.
First, containers must be outlined in a dad or mum aspect. This requires extra markup and makes the code considerably complicated and unintuitive. This code under may be a potential implementation:
.container {
container-type: inline-size;
}
.card {
> * { padding: 1cqbl; }
}
Coping with nested containers isn’t a lot an issue, as a result of we are able to at all times set the container-name
we wish to inherit from. However, there may be a collision if we wish to use completely different container references for cqbl
and cqw
.
Think about this:
Kinda sucks to be restricted by container collisions.
Anchor positioning syntax
On this case, we first determine the bottom we wish to inherit from. We are able to name this a base-anchor
, or one thing comparable.
Right here, we are able to explicitly set a base anchor identify — or even perhaps go away it as none
if we don’t wanna identify it. Then the remainder of the weather inside might inherit from this base worth instantly:
.card {
base-anchor: --card; /* or maybe none */
> * { padding: 1blh; }
}
If we have to discuss with this anchor from a totally unrelated part, we are able to leverage the anchor identify and easily do that:
.far-away-comp {
base-name: --card;
/* Then use blh from right here */
}
Double anchor
One fascinating side I can consider is a possible double-anchor use case the place the base
part is ready to inherit its font-size
or worth from one more base or its dad or mum aspect.
This flexibility lets us create part variations primarily based on font sizes extremely simply with no need to depend on complicated em
calculations.
Right here’s an instance of what I’m speaking about:
.prose {
base-anchor: --prose;
font-size: 1em;
line-height: 1.5;
}
/* Inherits font-size from .prose */
/* That is computerized if base-name just isn't offered */
.card {
base-anchor: none;
base-name: --prose;
/* On this case, 1blh could possibly be 1.5em */
> * { padding: 1blh; }
}
/* After inheriting the font measurement, since we now have a base-anchor within the card, we modify the font-size worth accordingly, so:
- 1bem would imply 0.8em additional within the card
- 1blh might then imply 0.8 * 1.5em = 1.2em
*/
.card.card-sm {
font-size: 0.8em;
}
Fascinating, yeah? This brings about a complete new risk when creating reusable parts.
Placing it into follow at the moment
Let me preface this part with the truth that bem
and blh
doesn't exist at the moment. So no matter implementation I can provide you with is solely an imperfect stop-gap measure.
In the present day, we're sure that we are able to use the em
unit for such a objective — however this requires a little bit bit extra calculation, since em
is a relative, not a base unit.
Step one is to find out the base
aspect — and the base
font measurement — which we are able to do by setting the base-size
property:
.card {
--base-size: 1em;
font-size: var(--base-size);
}
.card-sm {
--base-size: 0.8em;
}
We are able to then simulate the bem
(base em
) unit by dividing the supposed font-size
with the base-size
:
.card {
h2 {
--font-size: 2em;
font-size: calc(var(--font-size) / var(--base-size));
}
}
Sadly, the above code received’t work as a result of we are able to’t carry out a calc()
division with a unit-ed worth. So the very best we are able to do to take away the models from --base-size
.
Once we do that, we have to carry out one other calc()
on the bottom aspect to create the precise font-size
property:
.card {
--base-size: 1;
font-size: calc(var(--base-size) * 1em);
}
Then we carry out the identical calc()
within the
.card {
h2 {
--font-size: 2;
font-size: calc(var(--font-size) / var(--base-size) * 1em);
}
}
That is all beginning to get a little bit “ugh”.
No person desires to all these boilerplate code. So, that is finest abstracted away with a mixin, or even perhaps a operate. In case you use Sass, you may think one thing like this:
@mixin base-anchor() {
font-size: calc(var(--base-size) * 1em);
}
In case you use Tailwind, maybe you possibly can think about the Tailwind utility to do the identical. In any case, Tailwind utilities might be seen as handy Sass mixins.
@utility base-anchor {
font-size: calc(var(--base-size) * 1em);
}
We are able to then apply this utility into the bottom aspect. The code appears a little bit bit cleaner:
.card {
@apply base-anchor;
--base-size: 1;
}
.card-sm { --base-size: 0.8; }
For the
@utility text-relative {
font-size: calc(var(--text-size) / var(--base-size) * 1em);
}
We are able to then use the utility like this:
.card .title {
@apply text-relative;
--text-size: 2;
}
Now, to calculate the padding
of the cardboard for the .title
aspect, we have to reverse the font-size
to get the base-size
worth. That is finest accomplished with a CSS operate, which isn't extensively supported at the moment, however hopefully, quickly!
@operate --bem(--multiplier) {
consequence: calc(var(--text-size / var(--base-size) * 1em * --multiplier));
}
We are able to then use --bem
to calculate the padding on the cardboard title:
.card .title {
/* ... */
padding-block: --bem(0.5);
padding-inline: --bem(1);
}
We talked about above that the lh
worth works higher for margin and padding because it preserves vertical rhythm. So, why not create a --blh
operate too?
On this case, we are able to add a --leading
variable that the operate can inherit from:
@operate --blh(--multiplier, --lh-multiplier) {
consequence: calc(
var(
--text-size / var(--base-size) * 1em * --multiplier *
var(--lh-multiplier, var(--leading))
)
);
}
Then we are able to use --blh
like this:
.card .title {
/* ... */
padding-block: --blh(0.5);
padding-inline: --blh(1);
}
Within the spirit of at the moment
We are able to’t use --bem
and --blh
in manufacturing as a result of CSS Capabilities should not out there all browsers but. Within the spirit of creating bem
work proper now, we are able to create a utility, that calculates the --base-font-size
from the --font-size
.
Discover this new variable known as --base-font-size
, not --base-size
, since --base-size
is already used. (We can't overwrite the CSS variable.)
/* I multiplied the worth by 1em right here to make it straightforward so that you can use the worth */
@utility base-font-size {
--base-font-size: calc(var(--base-size) / var(--font-size) * 1em);
}
We are able to additionally create a utility referred to as base-line-height
to get the worth of the line-height
. Once we do that, it’s a lot simpler if we additionally cross in a --leading
variable:
@utility base-line-height {
--base-leading: calc(var(--base-font-size)* var(--leading));
}
Then we are able to use calc
on --base-leading
to get the values we would like:
.card .title {
@apply text-relative;
@apply base-font-size;
@apply base-line-height;
--font-size: 2;
padding-inline: var(--base-line-height);
padding-block: calc(var(--base-line-height) * 0.5);
}
Placing all of it collectively
Let’s first put collectively the required utilities and features to make this occur at the moment:
/* The required utilities */
@utility base-anchor {
font-size: calc(var(--base-size) * 1em);
}
@utility text-relative {
font-size: calc(var(--font-size) / var(--base-size) * 1em);
}
/* To make use of this at the moment */
@utility base-font-size {
--base-font-size: calc(var(--base-size) / var(--font-size) * 1em);
}
@utility base-line-height {
--base-line-height: calc(var(--base-font-size)* var(--leading));
}
/* Simpler utilization when CSS Capabilities develop into out there */
@operate --bem(--multiplier) {
consequence: calc(var(--font-size / var(--base-size) * 1em * --multiplier));
}
@operate --blh(--multiplier, --lh-multiplier) {
consequence: calc(
var(
--font-size / var(--base-size) * 1em * --multiplier *
var(--lh-multiplier, var(--leading))
)
);
}
Now right here’s the .card
code to realize the performance in Tailwind we had been speaking about. You possibly can see it at work right here.
/* What we are able to really use at the moment */
.card {
@apply base-anchor;
--base-size: 1;
--leading: 1.5;
> * { padding: 1lh; }
.title {
@apply text-relative;
@apply base-font-size;
@apply base-line-height;
--font-size: 2;
padding-inline: var(--base-line-height);
padding-block: calc(var(--base-line-height) * 0.5);
}
}
.card-sm {
--base-size: 0.8;
.title {
--font-size: 1.2;
}
}
/* What we are able to use when CSS Capabilities can be found */
.card {
@apply base-anchor;
--base-size: 1;
> * { padding: calc(--blh(1)); }
.title {
@apply text-relative;
--text-size: 2;
padding-block: calc(--blh(0.5));
}
}
It’s nonetheless not as fairly because the bem
and blh
variations I’ve proven you above, however on the very least, we obtain some type of performance, yeah? And it doesn’t look half unhealthy!
Utilizing this with Splendid Labz at the moment
Splendid Kinds — the department of Splendid Labz that handles design and types — comprises the code you should utilize at the moment.
We additionally included the --bem
and --blh
variations for those who wanna play with them as effectively.
To make use of Splendid Kinds, simply obtain the library, import the base-font-size
file, and do what you’ve simply seen the above!
npm i @splendidlabz/types
@import '@splendidlabz/types/typography/base-font-size.css'
That’s it!
Now, for those who’re focused on all the instruments I’m been cooking as much as make internet improvement less complicated, you possibly can seize an early chook low cost for the Splendid Professional package deal at the moment — that is out there to all CSS-Methods readers!
(I'd add a lifetime choice to the Kinds package deal because it evolves to sufficiently. However it may be a yr or so earlier than that occurs.)
Alright, sufficient promotion. Let’s come again right here.
What do you concentrate on this unit between root and relative values?
I hesitate to name it “base” em
as a result of “base” can imply so many issues. However it additionally sounds proper on the identical time.
- Does
bem
andblh
make sense to you? - Do you suppose I’m pondering a wee bit an excessive amount of for this design side?
- Possibly you’ve received a greater identify for this?
I’d love to listen to from you so please be happy to share your ideas under!