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 |
|---|---|
| 576px | 16px |
| 768px | 18px |
| 992px | 20px |
| 1200px | 24px |
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.