Setting the font size like clamp(1.875rem, 3vw, 3.75rem) defeats the purpose of using rem. We must throw calc() into the mix to make it user-friendly.
The clamp() function sets three sizes: a min size, an in-between size and a max size. A common example:
h1 {
font-size: clamp(1.875rem, 3vw, 3.75rem);
}
The second argument’s use of a single vw is problematic. It makes the in-between size relative to the viewport and not to the user’s settings. It leads to a shift and cancelling of the scaling. It also grows or shrinks too rapidly or too slowly.
In a 1000 px wide viewport, 3 vw is 30 px, which is 1.875 rem when the user has the font size settings at 100 %. The size will increase and reach its max at a 2000 px wide viewport. That’s when 3 vw equals 3.75 rem, which is 60 px at 100 %.
This system falls apart as soon as the user’s font setting is anything but 100 %, which we can see in a Codepen example with and without calc(). We actually don’t need the calc() function since clamp() already does calculations, but let’s keep it for the examples.
At 150 %, the clamp function with vw units has yet to start increasing in size.
Same amount of pixels is wrong #
At 150 %, the 1.875 rem minimum clamp is 45 px, which means the letters won’t get any bigger until 3 vw equals 45 px. That happens at 1500 px.
In this case, users with settings at 100 % and 150 % get the same pixel font size on viewports between 1500 and 2000 px. Tangents like these mean that the scaling is delayed or premature, defeating the purpose of using rems in the first place. The relative sizes are 1.875 rem for users at 150 % and 2.8125 rem for those at 100 %.
The font size doesn’t reach max until the viewport is 3000 px wide. Still fairly wide for most people. Settings above 100 % don’t mean bigger screens. It means that the user wants or needs bigger letters.
For users with smaller font settings, it’s the other way around. At 75 %, 1.875 rem is 22.5 px; the font size starts increasing at 750 px wide viewports and reaches max at 1500 px.
Users are not viewports #
We can make the in-between value relative to the users’ settings again by using calc() and adding rem to the mix.
h1 {
font-size: clamp(1.875rem, calc(1.875rem + 1vw), 3.75rem);
}
The value is made relative to the user settings and slightly relative to the viewport. The vw value should be smaller than if it was used alone because now we have a rem base. It also renders the minimum size somewhat useless because the user will never get below the rem size inside calc anyway. We can set the second argument’s rem value slightly lower than the first, but it might be unnecessary in most cases since we’re mostly talking about just a few pixels. I recommend testing and adjusting.
If we want to systematically make the second rem value smaller, we can calculate how many pixels the vw value equals at a small viewport, like 400 px. Which is 4 px or 0.25 rem. And subtract that from the rem value.
h1 {
font-size: clamp(1.875rem, calc(1.625rem + 1vw), 3.75rem);
}
For users with not 100 % font size, that number will be different but insignificant.
Realistic scale sans calc() #
There are no magic ratios for type. We can start with a scale, like the browser default, but we must use our eyes and dev tools and make adjustments until we can discern the different levels at different viewport sizes.
We set the first calc value below the min value and add a small vw increase. The bigger the font size, the bigger the dynamic value can be. For the max size, we find it by testing and adjusting.
The following scale is a useful starting point, and this time without the calc() function.
--txtl1: clamp(1.5rem, 1.35rem + 1.2vw, 2.4rem);
--txtl2: clamp(1.4rem, 1.3rem + 1vw, 2rem);
--txtl3: clamp(1.17rem, 1rem + 0.75vw, 1.5rem);
--txtbody: clamp(1rem, 0.9rem + 0.35vw, 1.2rem);
--txtsmall: clamp(0.9rem, 0.78rem + 0.35vw, 1rem);