Precise control over responsive typography

It is possible to have precise control over responsive typography. Using calc() and viewport units you can create fluid type that scales perfectly between specific pixel values, within a specific viewport range.

This text is limited to between 20px and 40px, over a viewport range of 600px to 800px.

I don’t know why we don’t see viewport units being used more extensively for creating designs with responsive typography.

Viewport units have been around since 2012 and are fairly well supported. In fact Internet Explorer was an early mover on this and supports viewport units as far back as IE9.

They are also really easy to understand. One viewport unit is simply 1% of the viewport and there are 4 types of viewport units:

So the reason viewport units are not used more extensively is probably not due to a lack of browser support or developers’ understanding. My guess is it’s probably more likely to do with the lack of precise control designers have over the font-size.

Designers that love typography often really love typography and they enjoy precise control over line-height, font-size, letter-spacing and other elements of typography those of us not in the club might not even know exist.

This desire for precise control is the reason that some designers still prefer to declare these properties using pixels. But it doesn’t really matter, whether they use ems, rems or percentages the truth is, they are all just abstractions of a base font size and that is usually 16 pixels. So they have never really had to give up complete control. It’s not difficult to work out what font-size an element is, as long as we know the base font-size.

But viewport units are different! They represent a fundamental change in approach. Unlike all the other units, viewport units are not relative to the base font size in any way. Instead they are relative to the viewport, which the user controls, and that might be scary for some.

But there are advantages to using viewport units, a font-size declared with viewport units is fluid, meaning it will scale smoothly. This is a clearly a better experience than clunky responsive typography techniques that require multiple media queries.

Responsive typography with viewport units is really easy to implement, just declare the base font-size using vw; as long as you are not using pixels elsewhere in your stylesheet, other units are relative to the base font-size, (which is now viewport units) so they will all scale accordingly.

But there are a few rough edges you will need to sand back. Firstly when you get down to a very small viewport scaling is problematic. Luckily there are a few good methods for avoiding this.

Limit font scaling with calc()

If you would like set an exact minimum font-size in pixels you can use calc().

:root{
  font-size: calc(16px + 3vw);
}

This example says set the default size to 16px + 3vw.

Note: There are still issues in some browsers when using viewport units and calc() together, so for now media queries is probably safer.

Limit font scaling with media queries

You can prevent the text from scaling below a specific threshold simply by using a media query and only applying viewport units above a certain device resolution.

:root { font-size: 18px;  /* default below 600px */ }
@media (min-width: 600px){
  :root {
    font-size: 3vw;
  }
}

We can also stop scaling above a specific font-size, but for this we need to first work out what the viewport size will be at the font-size we want to stop scaling. For that we need a bit of maths:

font-size / ( number of viewport units / 100 )
Eg. 24 / ( 3 / 100 ) = 800px

With that result just set another media query to change the root font-size back to a fixed unit.

...
@media (min-width: 800px){
  :root {
    font-size: 24px;  /*above 800px */
  }
}

The calculations are not that hard but I find it easier to look at a simple table. This helps me visualise the change in font-size across different resolutions.

Viewport units:1vw2vw3vw4vw5vw
Viewport sizefont-size in pixels
400px4px8px12px16px20px
500px5px10px15px20px25px
600px6px12px18px24px30px
700px7px14px21px28px35px
800px8px16px24px32px40px
900px9px18px27px36px45px
1000px10px20px30px40px50px

Looking at the table you can see there are many limitations. We have little control over the rate at which viewport units change and we are confined to the options available in the table.

Precise control with calc()

In his 2012 article on Fluid Type Trent Walton said:

"It's been hard to let go of setting a static font-size for a site and calling things done. I’m realizing that the predictability & control we've had over web type is becoming a thing of the past."

But perhaps not all predictability and control is lost.

Let’s imagine that as a typography nerd with an eye for absolute precision, you want the font-size at a resolution of 600px to be 12px. Great! Looking at the table, setting a font-size of 2vw will achieve this. But you also want the font-size at 800px to be 32px. It seems you can’t do this without changing from 2vw to 4vw and this means a break-point and our font scaling will be jumpy and not fluid. I consider this a pretty significant limitation.

There is a solution to this! It’s not exactly pretty but it works – at least in modern browsers. As stated earlier, some browser have bugs when using calc() and viewport units together, so this might be buggy in some older browsers. (This is not really a concern anymore, just set sensible default font sizes before declaring a fluid type calc() expression.)

It appears that by using calc() and vw we can get responsive typography that scales perfectly between specific pixel values within a specific viewport range.

This means you can have perfect smooth scaling between any 2 font sizes over any viewport range. The font will start scaling and stop scaling exactly where you want.

Try the demo: Precise control over responsive typography The demo uses SASS so you can easily change the upper and lower limits of the font-size and media queries. But the important part looks something like this:

  font-size: calc( 12px + (24 - 12) * ( (100vw - 400px) / ( 800 - 400) ));

Note: In the example above, 12px is the minimum font-size and 24px is the maximum. 400px is the start of the viewport range and 800px is where it should stop scaling. The inclusion or absence of the units after each value is important.

Put simply, it is a function that takes a value within a range and works out what the new value would be if applied to a different range. I can take the current viewport width (100vw) as input into this ‘function’. For example if I had viewport range of 500px to 1000px, and let’s imagine the current viewport is 750px, I then apply this to a font-size range. If my font-size range was 20px to 30px, because the input of 750px is right in the middle of 500px and 1000px my new font-size will also be right in the middle, 25px. Simple right?

This seems like it could be a pretty useful way to control the scaling of viewport units. It could also have uses beyond typography. You can do other interesting things, by inverting the range for example, you can have font sizes that get smaller as the viewport gets larger. Perhaps there is a use for this? I’d love to hear your thoughts and see other applications or extensions of this idea.

Update: Each of the methods above use pixels for ‘precise’ control, however some readers have expressed concern that this will override user preferences for default font size. This is true, however all methods also work equally well with rem or any other unit type.

More info