There are plenty of situations where we might want to change the font-size property of an element as the screen width changes. This makes sense. Someone viewing content on a smaller phone screen might be holding the device closer to their face whereas someone viewing that same content on a large desktop screen would be sitting further away. In each case, changing the size of the text can be a helpful tool to improve the overall user experience by improving the legibility of the content.

We could do that with media queries! Here's an example where the font-size increases as the viewport width does.

body {
  /* Never get smaller than this */
  font-size: 14px;
}

@media (min-width:600px) {
  body {
    font-size: 16px;
  }
}

@media (min-width:800px) {
  body {
    font-size: 18px;
  }
}

@media (min-width:1000px) {
  body {
    /* Never get larger than this */
    font-size: 20px;
  }
}

That's nice, but it's not really responsive. In other words, the font is jumping to different sizes to adapt to various screen sizes rather than responding and recalculating to any screen size. That latter scenario is what we call "fluid typography" and is what we want to achieve. It makes the browser do the hard work instead of us having to do the heavy lifting of defining and maintaining all of those media queries.

One way to get fluid typography is to change the font-size from pixels (px) to viewport units (vw). Doing so tells the browser to calculate the font based on a percentage of the current viewport width. Browser support is great for the vw unit making it a safe bet for use in most cases.

body {
  font-size: 2vw;
}

That tells the browser to calculate the size of the font as two percent of the viewport width. In other words, if the browser is 1000 pixels wide, then our font is the equivalent of 20px.

That's great, but now we're caught in a situation where the font-size can get infinitely smaller and larger. Dang it, now we're back to managing media queries.

But wait! If we use viewport units and calc(), we can have the font-size size based on the size of the screen while setting caps on how small and large the font can be. Rather than always being the same size, or jumping from one size to the next at media queries, the size can be fluid and within a defined range.

Here's the math, with credit to Mike Riethmuller:

body {
  font-size: calc([minimum size] + ([maximum size] - [minimum size]) * ((100vw - [minimum viewport width]) / ([maximum viewport width] - [minimum viewport width])));
}

For example, if we want the our font-size in a range where 14px is the minimum size at the smallest viewport width of 300px and where 26px is the maximum size at the largest viewport width of 1600px, then our equation looks like this:

body {
  font-size: calc(14px + (26 - 14) * ((100vw - 300px) / (1600 - 300)));
}

Heck yeah! That gives us a font-size that resizes itself to the width of the browser while setting caps on the minimum and maximum sizes without managing media queries.

We could stop there, but the one thing we have not talked about is the vertical rhythm of our text. Sure, the font-size changes with the browser, but the line-height does not. Fortunately, we can apply the same equation to the line-height property to accomplish that goal:

body {
  font-size: calc(14px + (26 - 14) * ((100vw - 300px) / (1600 - 300)));
  line-height: calc(1.3em + (1.5 - 1.2) * ((100vw - 300px)/(1600 - 300)));
}

Now, our font will scale in size and in line-spacing fluidly with the browser.

References