Viewport Sized Typography

Published by Chris Coyier

CSS3 has some new values for sizing things relative to the current viewport size: vw, vh, and vmin. It is relevant to bring up now, because it's shipping in Chrome 20 (canary at the time of this writing). And not behind a flag, it just works. Production usage isn't quite there, but it will be soon enough.

Why is this awesome?

There are many reasons. Here are two:

  1. There is a such thing as a comfortable line length for reading text on screens. I don't want to kick a hornet's nest, but let's say its around 80 characters. These units allow you to get it feeling perfect and then have that experience scale to any size screen.
  2. They allow you to tightly couple the size relationship of, say, a typographic header and the content it goes with. Like your classic Trent Walton style blog post.

How they work

One unit on any of the three values is 1% of the viewport axis. "Viewport" == browser window size == window object. If the viewport is 40cm wide, 1vw == 0.4cm.

For use with font-size, I guess it's one "letter" that takes on that size, but as we know, in non-mono-spaced fonts the width of a letter is rather arbitrary. I find you just need to tweak around with the values to get it how you want it. Which is basically what we do anyway, right?

1vw = 1% of viewport width
1vh = 1% of viewport height
1vmin = 1vw or 1vh, whichever is smaller
1vmax = 1vw or 1vh, whichever is larger

Using them

Easy cheezy:

h1 {
  font-size: 5.9vw;
}
h2 {
  font-size: 3.0vh;
}
p {
  font-size: 2vmin;
}

Demo

Here's a video of a simple layout using vw units for the font-size.

Check out the demo yourself (see browser support).

Or here's a GIF for a super simple header:

Bugs!

The support is there in Chrome 20+ / Safari 6+, but it fails in one rather significant way. When the browser window is resized, the font doesn't adjust itself according to the new viewport size. The spec says:

When the height or width of the viewport is changed, they are scaled accordingly.

I bugged it. Perhaps not a huge disaster as it's pretty much just us design nerds that go around adjusting browser windows, but still. The font does adjust on a fresh page load.

To fix this issue (allow resizing without page refresh) you need to cause a "repaint" on the element. I used jQuery and just fiddled with each elements (irrelevant, in this case) z-index value, which triggers the repaint.

causeRepaintsOn = $("h1, h2, h3, p");

$(window).resize(function() {
  causeRepaintsOn.css("z-index", 1);
});
UPDATE: Don't worry about this anymore and definitely don't be forcing those repaints. This resizing issue is fixed in Chrome 34+ and Safari 7+. It's not common to change the viewport size on mobile devices, so I'm not sure if this bug ever effected them or not.

Browser Support

IE 10+, Firefox 19+, Chrome 34+, Safari 7+, Android 4.4+, iOS 6+ - Supported

Chrome 20-34, Safari 6 - Supported but has repaint issue

There are a couple of other specific cross-browser weirdnesses with them, documented on Can I Use.

Not just font-size

For the record, these are just units. Just like em, px, whatever. You can use them on anything, not just font-size.

I think font-size is the most compelling use case, since things like margin, padding, and width can already essentially react to browser window size by using % units. There is the case where perhaps a more deeply-nested element needs to react to the browser window size instead of its direct parent size.

Using it now

Native usage

You'll at least want to provide a fallback:

h1 {
  font-size: 36px; /* Some tweener fallback that doesn't look awful */ 
  font-size: 5.4vw;  
}

Testing for support

Modernizr doesn't have a test for it yet, but you can test for it yourself by using some throw-away element that you see to some narrow width in CSS but then re-set to 100vw in JavaScript, then measure to see if the width of it is equal to window.width. Something like:

var testEl = $("#vw-test");

testEl.css({
  width: "100vw"
});

if (testEl.width() == window.innerWidth) {
   // Supported
} else {
   // Not Supported
};

Here's that test on CodePen, but note it only works in Full Page view otherwise the calculation might be off because of iframe issues.

Mimic the functionality with FitText.js

This idea of binding the overall width of a header with the width of its parent element is exactly what FitText.js does. Only it does it through fancy JavaScript and math and spans and stuff. Theoretically, you could run the test and use Modernizr.load to load up FitText.js if no support is detected.

More Information