Skip to main content

Intrinsic Typography

· 5 min read
Marvin Danig

Scaling the digital type across mediums has always been a difficult problem. In this post, we will talk about an intrinsically scaling typographic system that we have discovered while building our css framework.

As you'll see, this new system resolves most, if not all, of the challenges faced by a web developer. Let's start by solving this meme first:

CSS is Awesome!

CSS IS AWESOME!

We'll prevent the overflow and scale the type dynamically according to the dimensions of the element. Consider the following HTML:

<div class="box">
<p>CSS</p>
<p>is</p>
<p>awesome.</p>
</div>

I didn't have to use three separate p tags, but it offered additional control, so I just went with it.

Now we can start applying the styles like so:

@media only screen and (orientation: portrait) {
:root {
--vmin: calc(100vw/100);
}
}
@media only screen and (orientation: landscape) {
:root {
--vmin: calc(100vh/100);
}
}
:root {
--vmin: 1vmin;
}

From the code above, you can make out that it is our Orientation Query, and it demonstrates the mathematical relationship between the vmin and vw/vh units. vmin continuously points to the shorter side of the rectangular screen no matter what the orientation of viewing is.

Adopting the vmin.

From the code above, we can draw the following two axioms:

1st axiom:

  • 100vh in landscape mode = 100vw in portrait mode.

2nd axiom:

  • vmin is always the shorter side of the rectangle.

The first axiom is very simple. vw and vh units are interrelated and can be swapped easily according to viewport orientation. One can say that device orientation is intrinsic to viewport units.

The second axiom implies that a typographic unit based on the short side of the screen has the lowest delta, i.e., the least variation upon resizing the browser. Even as the window is resized to the point where the orientation is switched—from landscape to portrait—the vmin unit continues to be the lower of the two values smoothly, and it does so with the absolute minimum change in the value. Of course, vmax does the opposite—it reflects the maximum possible change in the unit value.

Returning to the meme problem, we know the box around the text is square. This means that its sides are equal. Also, remember—in the world of resizable rectangles, a square is that geometrical point of inflection where the viewport orientation flips.

*, *:after, *:before {
box-sizing: border-box;
margin: 0;
padding: 0;
}

@media only screen and (orientation: portrait) {
:root {
--vmin: calc(100vw/100);
}
}

@media only screen and (orientation: landscape) {
:root {
--vmin: calc(100vh/100); /* 100vh of landscape === 100vw of portrait. */
}
}
:root {
--vmin: 1vmin;
--side: calc(50 * var(--vmin)); /* dimensions of the square. */
--border: calc(2 * var(--vmin)) solid black; /* Outline for the square. */
}

.box {
width: var(--side);
height: var(--side);
border: var(--border);

}

This html and css will paint an empty square box on the page with a thick black border, just as it is on the meme.

Scoping the Font Size & Line Height.

Now let's apply an intrinsic font-size and line-height to the text within the box. To do so, we introduce two new variables, --fs & --lh, for font size and line height, respectively:


:root {
--vmin: 1vmin;
--side: calc(50 * var(--vmin)); /* dimensions of the square. */
--border: calc(2 * var(--vmin)) solid black; /* Outline for the square. */

--fs: calc(var(--side) / 6 ); /* Why the number 6, can you tell? */
--lh: calc(var(--fs) * 1.5);
}
.box {
width: var(--side);
height: var(--side);
border: var(--border);

}
.box p {
font-size: var(--fs);
line-height: var(--lh);
}

That's it. Our meme is now entirely resolved. And from now on, our community of css lovers can live free from this stain of incapacity css has had for such a long time. 🙏🏻

Dropping the Orientation Query.

The complete solution is as follows:

/* Baseline reset */
*, *:after, *:before {
box-sizing: border-box;
margin: 0;
padding: 0;
}

@media only screen and (orientation: portrait) {
:root {
--vmin: calc(100vw/100);
}
}

@media only screen and (orientation: landscape) {
:root {
--vmin: calc(100vh/100); /* 100vh of landscape === 100vw of portrait. */
}
}
:root {
--vmin: 1vmin;
--side: calc(50 * var(--vmin)); /* dimensions of the square. */
--border: calc(2 * var(--vmin)) solid black; /* Outline for the square. */

--fs: calc(var(--side) / 6 ); /* Why the number 6, can you tell? */
--lh: calc(var(--fs) * 1.5);
}
.box {
width: var(--side);
height: var(--side);
border: var(--border);

}
.box p {
font-size: var(--fs);
line-height: var(--lh);
}

If you observe the code above, the orientation query (from lines 8 to 18) isn't doing anything. We can remove it.

With this vmin based solution, the text in the box will scale correctly with sub-pixel accuracy no matter how much we resized the browser or even re-oriented the device. The scaling is so good that not even reflow isn't triggered on any of the modern browsers except maybe on Safari, where it works for the most part but has a little quirky behavior.

Feel free to test the demo.

Conclusion

Do you like this technique of scaling content with a simple vmin unit of css? I love the simplicity of this solution and its ability to help us avoid using combination of multiple css units to achieve the same/similar effect. It also helps me avoid using hardcoded media queries, hardcoded clamp(), and the unwieldily syntax of container queries, which requires yet another combination of alternative css units.

vmin is simple. And I think vmin can rule the entire space of Intrinsic Typography in the future. What are your thoughts? Have you tried web designing with vmin? If not, that's exactly what we'll talk about in our next post by extending our solution above to a more generic layout. Hang on tightly!