FrontAid CMS - Agile Content Management with JSON & Git.

» FrontAid CMS / Blog

Fluid Typography with two-dimensional CSS Locks

Websites are being used from the smallest smart watches to the largest TV screens. And the whole point of responsive webdesign is to adapt to all those devices. One of the things that thus has to be considered is sizing (e.g. font-size, width, height, margin, padding, …). How much spacing should be applied on desktop computers compared to mobile phones? What font sizes are useful on what devices?

It makes a lot of sense to have smaller spacings and slightly smaller fonts on mobile. The former because of the smaller viewport size and the latter because smaller devices tend to be closer to the user’s eyes. With regards to the font size, this approach is often called fluid typography. Traditionally, those adaptions to the screen size often have been done using Media Queries, mostly relying on the viewport width. But there is an easier way as we will show in this post.

Note that this website is a demo of that approach. You can see it by resizing the browser window; the font sizes and spacings will adapt fluently. On smaller windows, everything will be slightly smaller than on larger windows. And the font size will never be smaller or larger than their predefined values. All of that is possible without any breakpoint.

CSS Locks

“CSS Locks” is a mechanism to linearly scale a size in between a minimum and maximum bound. A common usage of CSS Locks is to scale font sizes depending on the current screen width. But as small font sizes are unreadable (and large ones are impractical), you also need to define a minimum (and maximum) size. You can read a quick introduction to CSS Locks or the full, detailed mathematical explanation in The math of CSS Locks.

Two-dimensional CSS Locks

While CSS Locks are usually based on the viewport width (or height) only, two-dimensional CSS Locks consider both dimensions at the same time. That is important because there are different aspect ratios of screens and also different orientations. For example, having different font sizes on a device in portrait mode compared to landscape mode does not make sense. But that is exactly what normally happens with ordinary CSS Locks as they are bound to width (or height) only.

In our example, we want to adapt spacings and font sizes automatically based on the viewport size. As we consider both the width and height, our styling will work equally well on displays in portrait or landscape mode.

Defining the Base Size

The perfect start to achieve responsive sizes given the viewport dimensions is by using the rem unit:

Represents the font-size of the root element (typically <html>). When used within the root element font-size, it represents its initial value (a common browser default is 16px, but user-defined preferences may modify this).

rems are evaluated relative to the root font size defined by either the browser or the website’s CSS. For example given a base font size of 16px, 1rem is equivalent to 1 × 16px and 1.5rem is equivalent to 1.5 × 16px = 24px. Below you can see an example of how you can use those units. Note that even though they are based on the root font-size, rem units can be applied to every property that is bound to a length or size like margin, width, or line-height.

html { font-size: 16px; }
body { font-size: 1rem; margin: 2rem; }
h1   { font-size: 2rem; }

With that setup it is easy to make sizes smaller or larger. The only thing you have to do is to update the font-size within the html selector. For example, changing it to html { font-size: 20px; } will increase all sizes that use the rem unit. As already mentioned, this step is most often done by using media queries as can be seen in the example below.

@media (max-width: 540px) { html { font-size: 16px; } }
@media (max-width: 720px) { html { font-size: 17px; } }
@media (max-width: 960px) { html { font-size: 18px; } }
@media (min-width: 961px) { html { font-size: 19px; } }

That approach is OK and well-known. But it is also known that those breakpoints can be arbitrary. Depending on your user’s devices, the sizing can thus be suboptimal. Especially shortly before approaching and shortly after reaching a break point. For example, your layout can work well on a screen with a width of 960 pixels. But smaller devices with a screen width of 721 pixels will have the exact same font size and spacings. The screen estate of the smaller device might thus be wasted with unnecessary spacings. The user might have to scroll more and their user experience suffers. And that just because their device is on the wrong side of a break point. This is exactly where two-dimensional CSS Locks come into play. They provide a way to have the base size scaled to the device and there are no hard break points anymore.

CSS em unit

The last section introduced the root font size that can be used as a base size for everything. But instead of using pixels as in the example before, we will use the em unit. This unit is similar to rem but refers to the inherited font size of the parent element. And if it is used on the root element—which itself does not have a parent element—it is based on the browser’s default font size.

The reason why we use em instead of pixels as the root font size is accessibility. All common web browsers provide settings where their users can configure their preferred font size(s). If we would set the root size in pixels, we would just ignore the user’s preferences. By using em instead, we base our root size on the user’s configuration. For example, html {font-size:1em} has the same effect as not setting a size at all: the browser will just fallback to the user’s default font size. html {font-size:1.2em}, however, slightly increases the default font size, whatever that may be.

By default, most browsers specify a font size of 16px. So 1.2em evaluates to 19.2px with that configuration. But if the user changes it to 20px, 1.2em would evaluate to 24px. As a result, we can slightly fine-tune the sizing of our website based on the user’s preferences.

CSS clamp() Function

In order to set up our own CSS Lock, we will be using the clamp() CSS function:

The clamp() CSS function clamps a value between an upper and lower bound. clamp() enables selecting a middle value within a range of values between a defined minimum and maximum. It takes three parameters: a minimum value, a preferred value, and a maximum allowed value.

clamp() is perfect for a CSS Lock as it does exactly what we want. Note that clamp() is a relatively recent addition to CSS and some older browsers don’t support it. But all of the important browsers support it just fine. So for our use case, we can use clamp() without any compatibility issue. Browsers that support it evaluate it correctly, and those that do not support it just ignore it.

2D CSS Locks

Specifying a CSS Lock is not trivial as the same values have to be repeated a couple of times, sometimes with a unit and sometimes without. Thus it is highly recommended to use CSS custom properties (also known as CSS Variables) to make it easier. In what follows, we will create a CSS expression using clamp() that calculates the root font size using three parameters which are defined as CSS properties. Note that all parameters are numbers without a unit and evaluated as em sizes/dimensions.

Defining a minimum font size is not even necessary. We just use 1em for that which is identical to the user’s preference. That means that the base size is always either exactly the value specified in the browser configuration, or it is a slightly larger value. But it is never smaller than 1em.

html {
  --maxSize: 1.25; /* maximum font size in `em` */
  --minDim: 20;    /* minimal viewport in `em` */
  --maxDim: 75;    /* maximum viewport in `em` */
  font-size: clamp(
    1em,
    calc(
      1em
      + (var(--maxSize) - 1)
      * (100vmin - var(--minDim) * 1em)
      / (var(--maxDim) - var(--minDim))
    ),
    calc(var(--maxSize) * 1em)
  );
}
body { font-size: 1rem; margin: 2rem; }
h1   { font-size: 2rem; }

That expression looks a little daunting, so let’s break it down. The font-size is declared as clamp(minSize, preferredSize, maxSize). Given the example parameters, preferredSize will be evaluated as calc(1em + (1.25 - 1) * (100vmin - 20em) / (75 - 20)). That is exactly the formula that was explained in The math of CSS Locks. In this example the default (font) size of 1em will be applied for viewport dimensions smaller or equal to 20em. The maximum (font) size of 1.25em will be applied for viewport dimensions larger than 75em. In between viewport dimensions of 20 to 75 em, the base size will scale linearly from 1 to 1.25 em.

Note that we mentioned “viewport dimension”, and not just width or height. It is a two-dimensional CSS Lock that is applied based on the smaller dimension of either the width or the height. That is being achieved by using 100vmin (see CSS length units for details).

Now that we have defined a root size on the html element, we can use the rem unit whenever we want to define a (font) size. You can change the parameters that we used however you want. Just be aware that a common choice for the minimum font size is usually “16px” and thus most font-size-related rem rules should be 1 or higher.

Wrap-Up

Smaller devices have less screen estate and tend to be closer to the user’s eyes. Both of those reasons encourage us to decrease spacings and font sizes (fluid typography) accordingly. But using media queries or regular one-dimensional CSS Locks may cause suboptimal usage of the screen—especially on widescreens or mobile devices in landscape mode. Two-dimensional CSS Locks thus consider both the viewport width and height to fully adapt to all screens including their orientations. When used together with the rem unit, we can make sizing in CSS much easier.