This website is built using Utopia scaling, which means that, instead of having multiple hard breakpoints, there are only two: phone and desktop, and none of those breakpoints controls sizing or spacing. The only thing my breakpoints do is change the layout to accommodate the screen space expected in a landscape or portrait setting. Utopia scaling automatically reduces the spacing and font sizes on smaller devices, removing whitespace in a controlled way that corresponds to the trained expectation that phones are small but with excellent screens that make reading on them more comfortable than anyone might have expected 20 years ago.

But I never really understood the math behind it. A few weeks ago, I chose to fix that, and I think (I think, mind you) that I understand it now. It's all linear geometry. Let me explain how, for a chosen base font size, you calculate the base font sizes for your smallest screens, your largest screens, and a smooth, fractionally accurate range of font sizes for every possible screen size in between.

Traditionally, breakpoints are set at four positions. A common table of them might look like:

Screen width Font Size
576px16px
768px18px
992px20px
1200px24px

The idea is that anything at or below that screen width uses the font size, so phones would get 16px, tablets 18px, and so on.

Utopia scaling is relative: there's math involved. In fact, there's so much math that I felt it was essentially my duty to figure out what the hell this meant:

  --step--1: clamp(0.625rem, 0.5177rem + 0.5366vw, 0.9rem);
  --step-0: clamp(0.75rem, 0.6037rem + 0.7317vw, 1.125rem);
  --step-1: clamp(0.9rem, 0.7024rem + 0.9878vw, 1.4063rem);

Those are the font size declarations for this website, the one you're reading right now. The CSS variable --step-0 is my base font size. It's essentially 1rem, but it can go as small as 0.81rem and as large as 1.4rem (so about 12px on a 320px-wide phone, and 18px on a 1140px-wide desktop screen), but the magic is that anywhere in between those sizes of screen the size of the font is a precise linear proportion. To see it in action, if you're on a desktop, just grab one of the sides of the browser frame and play with the width, and the font and spacing should change dynamically with your decisions.

The two other sizes, --step--1 and --step-1, are smaller and larger, respectively. But I didn't understand how these numbers were generated and, frankly, the explanations on the Utopia website didn't help. Worse, the font sizes use two different "scales," one for the small end and one for the large, but what did they mean?

Well, I'm here to tell you.

Just a word of warning: We're not getting into the scales today, we're just figuring out the step 0 calculation. But that's an essential starting point, so let's get to it.

The Baseline

First, all of my sizes are in rem. Bad design sites will tell you that a rem is 16px, but it is not. A rem is the "default size" the browser uses when a page doesn't specify; most browsers do ship with 16px as that default size, but it can be changed (in Chrome: chrome://settings/fonts; in Firefox, about:preferences and scroll down to "Fonts"), and it should be more visibly changeable as the population of web users ages, and websites must adapt to whatever size the user needs.

But it's a rem, whatever it is. So let's start with two assumptions: most websites do specify a font's base size for their "reading" material, and I want that size on small devices to be 75% of the base size, and on large devices 112.5% of the base size. This is the size named "step 0", or --step-0 in the CSS variable form. I'm going to specify that the 75% size is the minimum, and true for any device less than 320 pixels wide, and that the 112.5% size is the maximum, and true for any device more than 1140 pixels wide.

Plunk that down on a graph and it looks a bit like this:

[graph1.png]

In fact, what we're looking for is a straight-up linear formula. If you remember your geometry, every straight line has a slope, and in Utopia scaling the font size scales with that slope. In the example above (clamp(0.75rem, 0.6037rem + 0.7317vw, 1.125rem);), we use the CSS clamp() function to say "Go no smaller than 75% of rem, and go no larger than 112.5% of rem, and in between use this formula." That formula has two parts: the slope, which specifies how the font will scale, and the y-intercept, which is where the line hits the y-axis. (In the graph above, the x-axis is the screen size, and we want to know what font size we would reach without that clamp when the screen size goes to zero, so we want the y-axis intercept, because the y-axis is the font size we care about.)

The Slope

The vw unit, the viewport unit is "1% of the screen's total width," so a 1vw on a 1280pixels screen is 12.8pixels. This makes it the useful metric for doing the math; in the example above, if your tablet is a 768pixel-wide device, your font size becomes (0.73 * 7.68)px + 0.60rem, and if you haven't changed your default font size, that works out to a base font size on that device of 15 pixels.

Calculating the slope is actually the easy part. Let's call our variables low_screenwidth, low_fontsize, high_screenwidth, and high_fontsize. The slope becomes:

scale_factor = (high_fontsize - low_fontsize) / (high_screenwidth - low_screenwidth)
scale_factor = (18 - 12) / (1140 - 320)
scale_factor = 0.007317

But remember that viewport widths are percentages, and we want our formula to correspond to working with hundredths-of-a-unit, so we multiply by a hundred to get our slope: 0.7317.

Which corresponds with Utopia's values.

The Intercept

The intercept is where the line intercepts the Y-axis as it slopes downward, and it's the starting point for all your calculations. The one thing that messed me up is the base size looks like it's entirely calculable in terms of pixels, but it's not. Or rather, it's based on an assumption that you know what your base font size "is", and that this nifty linear algorithm is just there to help you fit your size around the reality that different devices have different sizes and qualities of screens. I've chosen a size between 12 and 18, with a "sweet spot" for most devices at, yes, 16 pixels.

start_fontsize = (low_fontsize - (scale_factor * low_screenwidth)) / base_size;
start_fontsize = (12 - (0.007317 * 320)) / 16;
start_fontsize = 0.6037

Which, again, corresponds nicely with Utopia's numbers.

And with that, it becomes possible to calculate our base font and stick it in a CSS Custom Property to use wherever we want:

  --step-0: clamp(0.75rem, 0.6037rem + 0.7317vw, 1.125rem);

And despite the assumption of 16 pixels, this font size scales very nicely with any browser's base font-size setting (or any override of that setting in your style sheet for the document's base font size, but please don't do that, it's an ARIA violation).

Conclusion

In this post, we discussed how Utopia scaling's math works: how we find the scale factor between our two screen sizes and font sizes, treating them as two points on a line in the format (screen size, font size), and finding the slope between them, then using that slope to calculate the intercept, giving us a formula for smooth scaling on a variety of devices.

In the next post, we'll discuss how to create the steps; what a "step" means in the discipline of this system. After that, we'll discuss the spacing system, which is almost exactly like the step system, but it has a simplifying twist, and finally, we'll automate a t-shirt sizing system for both fonts and spacing. That system will have a fixed number of steps (say, from negative -3 to +5, giving us 9 steps total), but within those steps font sizes and spacing will be programmable with a handful (about 8) CSS Custom Properties.