Introduction
If you want to change font sizes fluidly responsive to the screen size, you can do so by using units such as vw
. For example, if you set font-size: 5vw;
, the font size will be 48px
for a viewport width of 960px
, and 16px
for 320px
. However, if you feel 16px
is too small for 320px
and want to make it 32px
, you will need to do some calculations.
For more details about fluid typography and the calculation, please refer to the slide from my old presentation if you can read Japanese:
Or, you can read the following English articles:
I've also made a web app that generates CSS with the calculation:
And a Sass mixin:
ixkaito / viewportscale
To linearly scale font-size, margin, padding, etc. across viewport widths.
In this article, I'll show you how to implement fluid typography using calc()
, clamp()
, min()
, max()
and CSS custom properties, so that you don't need to calculate every time by yourself.
Source code and demo
How to Use
First, copy and paste the following code into your CSS. The comment part is optional but helps you understand how to use. Then, all you have to do is to set necessary CSS custom properties to any elements, referring to the documentation.
/**
* Available vars:
* @var --viewport-from: <number> - Number in pixels without the unit. Required if `--font-size` is not exist.
* @var --viewport-to: <number> - Number in pixels without the unit. Required if `--font-size` is not exist.
* @var --font-size-from: <number> - Number in pixels without the unit. Required if `--font-size` and `--min-font-size` is not exist.
* @var --font-size-to: <number> - Number in pixels without the unit. Required if `--font-size` and `--max-font-size` is not exist.
* @var --max-font-size: <number> - Number in pixels without the unit. Optional.
* @var --min-font-size: <number> - Number in pixels without the unit. Optional.
* @var --viewport-unit-converter: 1vw | 1vh | 1vmin | 1vmax - Optional. Default: 1vw.
* @var --font-size: <length> | <percentage> | <absolute-size> | <relative-size> | Global values - Optional.
*/
*, ::before, ::after {
--viewport-unit-converter: 1vw;
--fz-from: var(--font-size-from, var(--min-font-size));
--fz-to: var(--font-size-to, var(--max-font-size));
--fz-slope: (var(--fz-to) - var(--fz-from)) / (var(--viewport-to) - var(--viewport-from)) * 100;
--fz-intercept: (var(--viewport-to) * var(--fz-from) - var(--viewport-from) * var(--fz-to)) / (var(--viewport-to) - var(--viewport-from));
--font-size: calc(var(--fz-slope) * var(--viewport-unit-converter) + var(--fz-intercept) * 1px);
--min-fz-px: calc(var(--min-font-size) * 1px);
--max-fz-px: calc(var(--max-font-size) * 1px);
--clamp: clamp(var(--min-fz-px), var(--font-size), var(--max-fz-px));
--max: var(--has-max, var(--min));
--min: var(--has-min, var(--font-size));
--has-max: min(var(--max-fz-px), var(--font-size));
--has-min: max(var(--min-fz-px), var(--font-size));
font-size: var(--clamp, var(--max));
}
Example 1:
h1 {
--viewport-from: 320;
--font-size-from: 32;
--viewport-to: 960;
--font-size-to: 48;
}
In this case, the font size will be 32px
for a viewport width of 320px
and 48px
for 960px
without minimum and maximum values. The font size will keep getting smaller or bigger, even the viewport is smaller than 320px
or larger than 960px
.
Example 2:
h1 {
--viewport-from: 320;
--viewport-to: 960;
--min-font-size: 32;
--max-font-size: 48;
}
This has the same rate of change as example 1, but the font size will not be smaller than 32px
or bigger than 48px
.
h1 {
--viewport-from: 320;
--font-size-from: 32;
--viewport-to: 960;
--font-size-to: 48;
--min-font-size: 32;
--max-font-size: 48;
}
This code gives you the same result. You can omit --font-size-from
and --font-size-to
if --font-size-from
and --min-font-size
, and --font-size-to
and --max-font-size
have the same value.
Example 3:
h1 {
--viewport-from: 320;
--viewport-to: 960;
--min-font-size: 32;
--font-size-to: 48;
}
You can also specify only the minimum or maximum value.
Example 4:
h1 {
--viewport-from: 320;
--font-size-from: 32;
--viewport-to: 960;
--font-size-to: 48;
--min-font-size: 36;
--max-font-size: 40;
}
--font-size-from
and --min-font-size
, and --font-size-to
and --max-font-size
can have different values. In this case, the font size will change from 32px
for 320px
to 48px
for 960px
; actually, it will not be smaller than 36px
and bigger than 40px
.
Example 5:
h1 {
--viewport-from: 320;
--font-size-from: 32;
--viewport-to: 960;
--font-size-to: 48;
}
@media (min-width: 960px) {
h1 {
--viewport-from: 960;
--font-size-from: 48;
--viewport-to: 1920;
--font-size-to: 64;
}
}
If you use media queries, you can change the rate of change in the middle. If the above code didn't have the media query part, the font size would be 72px
at 1920px
.
Example 6:
h1 {
--viewport-from: 320;
--font-size-from: 48;
--viewport-to: 960;
--font-size-to: 32;
--min-font-size: 16;
}
It is also possible to change from a large size to a small size.
Example 7:
h1 {
--font-size: 2rem;
}
You can also set a constant value. If --font-size
is specified, all other properties are optional.
Example 8:
h1 {
--font-size: calc(2.5vw + 24px);
}
You can also specify your own formula.
Example 9:
h1 {
--font-size: calc(2.5vw + 24px);
--min-font-size: 32;
--max-font-size: 48;
}
You can also use your own formula with maximum and minimum values.
Example 10:
h1 {
--viewport-unit-converter: 1vh;
--viewport-from: 320;
--font-size-from: 32;
--viewport-to: 960;
--font-size-to: 48;
}
By default, vw
is used to calculate for the viewport width, but you can also use vh
, vmin
or vmax
. Please note that you need to add 1
to each unit, as in 1vh
.
I hope these examples cover most of the usage.
Explanation
The formula is the same as in the above articles, so let's skip it.
Point 1
If we set the initial value and formulas of CSS custom property to :root
, we will not get the expected result because of the value inheritance. We have to set them to *
to apply them to all elements.
Point 2
The CSS calc()
can add and subtract values with units, but not multiply and divide them. We need to append units after calculating the unitless values. Therefore, most of the properties need to be set without units.
We can append a unit by multiplying by 1
with a unit, as in var(--fz-intercept) * 1px
. This is why it has to be 1vh
in example 10.
Point 3
For now, CSS doesn't have conditional expressions. How I did to determine whether maximum or minimum values are specified or not is using clamp()
, min()
and max()
with the CSS custom property fallback. clamp()
, min()
and max()
return an empty value if arguments are not passed correctly. If the return value is empty, the custom property will refer to the fallback.
Conclusion
This is a byproduct of my other project, but I think it turned out to be quite useful. I hope this helps. Thank you for reading my article.
Top comments (0)