loading...
Cover image for Using CSS viewport units (vw, vh ...) for font sizes can be tricky

Using CSS viewport units (vw, vh ...) for font sizes can be tricky

macargnelutti profile image Matteo Cargnelutti Updated on ・5 min read

CSS viewport units such as vw and vh are extremely useful, as they provide a way of sizing things according to the current browser's viewport dimensions. Their convenience make it tempting to size everything using them, including text: this means less media queries with arbitrary break points, and perfectly consistent look and feel across viewports.

But what are the implications of making everything scale using simply viewport units?


An example

Consider this basic web page, displayed in a 1440px-wide viewport.

Example 1 displayed in a 1440px-wide viewport

In this example, a font-size is defined on the document's root using vw, and all the other elements use rem as their length unit, which basically translates as "use the font-size defined for <html> to determine the size of x". As such, the dimensions of most elements on the page are directly connected to the font-size of <html>.

html {
  font-size: 1.5vw;
}

[...]

main > h1 {
  display: inline-block;
  font-size: 1.75rem;
  padding: 1rem;
  background-color: black;
  color: white;
}

main > p {
  font-size: 1rem;
  line-height: 1.25rem;
  padding-bottom: 1rem;
}

Exception to the rule, our main content container is sized using vw, so it always take 60% of the viewport width.

main {
  width: 60vw;
  margin: auto;
}

With this setup, since everything scales, it should look exactly the same regardless of the resolution. And sure enough, it does.

Here is the same web page displayed in a 960px-wide viewport.

Example 1 displayed in a 960px-wide viewport

And once again in a 4800px-wide viewport.

Example 1 displayed in a 4800px-wide viewport

Problem #1: the browser's zoom feature no longer works

To simulate different viewport sizes, I simply used Firefox's zoom in/out feature. While it proved my point that everything scales seamlessly, it also highlighted one major issue: the user can no longer make the text bigger by zooming-in!

This is of course a usability / accessibility issue we want to prevent. This behavior can be "somewhat fine" though if every single piece of copy on the screen is massively big, which is not the case here.

Problem #2: Resizing the browser's window impacts readability

Since the font-size of everything is based on the viewport's width in this example, if I want to put two windows side-by-side by reducing their width, I end up shrinking everything considerably, and the content becomes much harder to read.

Imagine the following on a 13" laptop screen:
Example 1 on a side-by-side setting with odd resolutions

This is even more problematic considering that the browser's zoom no longer works!

Problem #3: the text will look massive on a large screen

On the last of the three screenshots, my viewport is 4800px wide. It still looks perfectly fine on my 13" screen, but what about people viewing this page on a 32" screen or bigger?

Well it will look exactly as shown on the screenshot, but on a massive 32" screen, with characters way too big, and the content container being 19.2" wide ! Unless we want some of our visitors to look like they are watching tennis when they read our content, we might want to avoid this.


A solution: media queries to the rescue!

In this example, the only place we are giving a direct value to font-size is in <html>'s style definition. This is very convenient, because then we could simply use rem everywhere else to refer to that font-size and make everything scale, but that is exactly the cause of the problems described above.

What if we want some scalability, but make sure content is still readable in almost all cases, while also having the zoom feature working?

The first thing we could do is add media-queries to <html> to change the value of font-size based on different parameters: current viewport width, is the viewport height bigger than the viewport width, etc.
We could still use vw, vh or even vmax for this, and that would probably work and make for still very smooth scaling. I personally prefer having to write less (and simpler) media queries by using px for font-sizes: by using a set of absolute values instead of viewport units here, I don't have to account for cases where the height is bigger than the width, for example.

Let's change how our example handles font-size:

html {
  font-size: 24px;
}

@media (max-width: 1921px) {
  html {
    font-size: 22px;
  }
}

@media (max-width: 1441px) {
  html {
    font-size: 20px;
  }
}

@media (max-width: 1025px) {
  html {
    font-size: 18px;
  }
}

Before we test this out, let's add another rule: since the font-size won't scale as drastically as before, and that my content container is still 60% of the width of the screen, I don't want to end up with very long lines of text.

To fix this, I can use ch to constraint the maximum width of the content:

main {
  width: 60%;
  max-width: 70ch;
  margin: auto;
}

By using max-width: 70ch, I tell the browser "I want <main> to be at most 70 times the size of the 0 character of the current font.", which roughly makes for lines of up to 80-ish characters. (See Eric Meyer's article on ch for more details on how this works)

This is how it looks in a 1440px-wide viewport.

Example 2 displayed in a 1440px-wide viewport

At 960px.

Example 2 displayed in a 960px-wide viewport

At 4800px (imagine this on an ultra-wide screen)

Example 2 displayed in a 4800px-wide viewport

And finally in "side by side" mode with "odd" viewport dimensions. (I added a simple media query to make the content take the whole viewport width under 768px)

Example 2 in a side-by-side setting and odd resolutions

A few media queries did the trick: things no longer scale as smoothly, but they do scale properly, and content is readable in all cases.


Conclusion

Viewport units are a fantastic addition to CSS's arsenal: they are convenient and finally made the idea of having a "fold" on the page achievable without using a single line of JavaScript.

The goal of this article is not to try and dissuade anybody from using them (on the contrary !), but to offer a perspective on what are the possible pitfalls of using them to make everything scale, and what could be the steps from preventing them.

Posted on by:

macargnelutti profile

Matteo Cargnelutti

@macargnelutti

CTO & Software Developer at Grafton Studio, Boston MA. Interested in everything Web standards, Python, Javascript. He/Him, πŸ‡ΊπŸ‡ΈπŸ‡«πŸ‡·.

Discussion

markdown guide
 

At Matise we added a Mixin to our scss framework to be able to add min and max font size based on our vw grid, it sets media queries automagically ✨
github.com/MatiseAms/matise-gryd/b...

 

Clamp should come in handy here..

font-size: clamp(1rem, 2.5vw, 1.5rem;

developer.mozilla.org/en-US/docs/W...

 

Big time ! I don't know if this feature will become standard anytime soon though.

 
 

I think if you do

html {
font-size: 62.5%
}

then using rem becomes easier because 1rem = 10px

not sure my comment is even relevant.

 

A tldr, never use pixels, they're ignorant. Use rem's for size and spacing and em's for media queries.

 

Thanks for your comment !
There is definitely some truth to that, but there are cases where their behavior can be properly anticipated and be useful :) I guess it's a matter of use case and what you are trying to achieve.