Fluid Typography Using CSS Custom Properties (CSS Variables)
Tags: css, html, webdev, frontenddev
Published: 2022-01-31 10:17:51

---
title: Fluid Typography Using CSS Custom Properties (CSS Variables)
published: true
description: We’ll explore the implementation of fluid typography with CSS custom properties, clamp(), min() and max().
tags: css, html, webdev, frontenddev
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/exfo4n9kgwadwuu9ojr8.png
---

## 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:

- [Typography Basic](https://speakerdeck.com/ixkaito/taipogurahuibesituku-plus-dezainaidea?slide=51)

Or, you can read the following English articles:

- [Fluid Typography](https://css-tricks.com/snippets/css/fluid-typography/)
- [Simplified Fluid Typography](https://css-tricks.com/simplified-fluid-typography/)
- [Modern Fluid Typography Using CSS Clamp](https://www.smashingmagazine.com/2022/01/modern-fluid-typography-css-clamp/)

I've also made a web app that generates CSS with the calculation:

- [ViewportScaler](https://ixkaito.github.io/viewportscaler/)

And a Sass mixin:

{% github ixkaito/viewportscale no-readme %}

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

{% codepen https://codepen.io/ixkaito/pen/bGoPaEQ default-tab=css,result %}

## 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.

```css
/**
* 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:

```css
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:

```css
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`.

```css
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:

```css
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:

```css
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:

```css
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:

```css
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:

```css
h1 {
  --font-size: 2rem;
}
```

You can also set a constant value. If `--font-size` is specified, all other properties are optional.

### Example 8:

```css
h1 {
  --font-size: calc(2.5vw + 24px);
}
```

You can also specify your own formula.

### Example 9:

```css
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:

```css
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.