DEV Community

Cover image for Fluid Typography Using CSS Custom Properties (CSS Variables)
Kite
Kite

Posted on • Updated on

Fluid Typography Using CSS Custom Properties (CSS Variables)

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:

GitHub logo 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));
}
Enter fullscreen mode Exit fullscreen mode

Example 1:

h1 {
  --viewport-from: 320;
  --font-size-from: 32;
  --viewport-to: 960;
  --font-size-to: 48;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

--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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

It is also possible to change from a large size to a small size.

Example 7:

h1 {
  --font-size: 2rem;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

You can also specify your own formula.

Example 9:

h1 {
  --font-size: calc(2.5vw + 24px);
  --min-font-size: 32;
  --max-font-size: 48;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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)