Web Fonts &
Typography News

Issue #23 • 8 August, 2019

This week has just flown by—I’m not entirely sure how it came to be Thursday already! Planning is in full swing for fall conference travel with flights booked for events from Tokyo to Toronto, plus a few other places around and in between. Just this morning I’m able to announce a new one: I’ll be heading to Paris in December to speak at dotCSS!

As I mentioned last week, there are still seats available for my workshops in Tokyo, Toronto, and Berlin this fall—so follow the links if one of those fits your schedule. Of course, if you’d like to find out about a talk or workshop for your own team, hit reply and I’d love to chat about it. Now, on to what’s coming this week.

For this issue of the newsletter, I feel like I should forewarn my less code-obsessed readers.

This has a lot of code

But don’t worry: you can skip around those bits easily enough to read the explanations of what's going on and just check out the example images and live demo on CodePen.

The reason for all this is because it’s the first time I’ve written an explanation of what I’ve been speaking about for the past year—and I hope will be the basis for a larger effort. But this should serve as a solid foundation to explain the concepts behind ‘dynamic typography’ (and it's relationship to and extension of responsive typography).

Lastly, I'd like to acknowledge a bit of a milestone. This week marks the first time that list membership has topped 800. And for something that has grown totally organically over the past five months or so, I'm proud and more than a little pleased that our community has grown so much. With weekly positive feedback, an average of over 45% open rate, and click rate of almost 5.5%, I feel confident that it’s connecting well and that the content itself is useful.  Many thanks for reading, and please do continue to get in touch with feedback, suggestions, questions, and conversation.



Replies go straight to my inbox—let me know what you want to learn!

Today’s Tip

Digging in to dynamic typography

The core idea of responsive typography is that the main elements of typographic structure and hierarchy should scale based on viewport size. Small screens can have more subtle size or weight distinctions and still convey hierarchy. Reading comfort is best served by keeping font size, line-height, and line length in sync: tighter line-height (leading) for narrower columns; more open line-height spacing as the line length gets longer on bigger screens. It also aids legibility to modulate font-weight for headings as font size changes. The smaller the physical size, the more you might want to reduce the weight in order to maintain legibility (reducing weight from 700 to 625 on headings for example). It can also be useful (if it’s available) to slightly reduce the width of the text on smaller screens to fit a few more characters per line. This is especially useful with larger heading sizes.

Previously, I’ve always recommended using a series of breakpoints to adjust each of these parameters as the screen size changes. But this require a lot of code, and can be hard to maintain and keep in sync across different HTML elements.

This table is taken from my 2014 book Responsive Typography. It shows scaling various elements based on device size using media queries

As I showed last week and have been demonstrating in talks over the past year or so, new CSS techniques are opening up possibilities for greater fluidity with less code. Variable fonts let us do even more. This technique uses calculations in CSS (and augmented with JS) to scale elements such as font-size, line-height, font-weight, and font-stretch (width) fluidly from a supplied lower value to a higher one, starting at some breakpoint and capping the scale at a larger one. This prevents the text from ever getting smaller or lighter or narrower than the lower value, or bigger than the higher one.

Screenshot of big and small screen views
Our demo page on large screen and small. Not the scale in size, width, and weight

This time I’ve eliminated all of the extra code and focused solely on the aspects of scaling the typography (you can follow along on CodePen). We can build on this in the future to layer in font loading, fallback font tuning, static web font support with the variable fonts scoped within an @supports block, and more complete abstraction to use the same CSS and JS with different font stacks and size ranges. But this is a bit more constrained. For the curious, I’m also featuring another of Monotype’s upcoming releases, Frutiger Neue Variable. It’s a real beauty, and the width and weight axes give a huge amount of range to a real workhorse of a typeface.

Image of demo in the CodePen interface
Dynamic typography demo on CodePen, featuring Frutiger Neue Variable from Monotype

Last week’s example included scaling font-size and line-height with calculations, but font-weight and font-stretch were set statically for small, medium, and large values—spread across breakpoints. The reason for that is by their nature, CSS calculations like these have a unit value once the calculation is completed. That works for font-size and line-height, but not for font-wight (needs to be just a number) or font-stretch (which must be expressed as a percentage). While the CSS specification is updating to help you arrive at a unit-less value from a calculation, that capability does not yet exist in browsers. So this week I’ve added some JavaScript that I adapted from Mandy Michael’s Fluid Font Weight demo on CodePen

The adaptations were made to allow us to feed in multiple sets of variables to scale values for font-weight and font-stretch in the same manner as font-size and line-height. Since we set the low and high end values in our element declarations, we don’t have to worry about the JS not working—but when it does, those values will scale as smoothly as everything else.

The setup

To get started we need to establish some initial variable values to be used in our calculations. Things like breakpoints; font size, line height, width, and weight value ranges; and the default starting point assignments for each element. We’ll look at breakpoints and the main heading (h1), but it’s the same for ‘h2’ and paragraphs.

:root {

  /* breakpoint variables */
  --bp-small: 25;
  --bp-medium: 45;
  --bp-large: 55;
  --bp-xlarge: 65;

  /* set px value sizes */
  /* initial px value for text */
  --rem-px: 16;

  /* EDIT HERE to set px value sizes */
  --h1-size-s-px: 40;
  --h1-size-l-px: 72;
  /* calcs to create values for the formulas */
  --h1-size-min: calc( var(--h1-size-s-px) / var(--rem-px) );
  --h1-size-max: calc( var(--h1-size-l-px) / var(--rem-px) );
  --h1-lh-min: 1.05;
  --h1-lh-max: 1.1;
  /* values to scale across breakpoint range */
  --h1-wdth-min: 75;
  --h1-wdth-max: 85;
  --h1-wght-min: 700;
  --h1-wght-max: 225;

  /* default starting scale value */
  --h1-wdth: var(--h1-wdth-min);
  --h1-wght: var(--h1-wght-min);

Walking down through the code above, we first set breakpoint variables in ‘em’ units, but just as numerals. That’s because we’ll need to do some math with them, so we’ll add the ‘em’ when we need it. Then you’ll see a value for the base text size (we’re setting things up with 16 as the base pixel equivalent). Next we set the pixel values for the low and high end of the size range for the ‘h1’ element. Now we combine them to end up with ‘em’ values to use in our formula, low and high values for line height scaling, width, and weight. Finally, we set the low values as the default values to start with. This last bit is important: these two custom property assignments are the ones we’ll be dynamically reassigning as the viewport changes with our JavaScript.

Staring small(screen)

Once we have the initial values assigned, we can use them to set up styles for the main heading on small screens, like so:

h1 {
  font-family: var(--font-stack-heading);

  /* set low-end scale values for smallest screens */
  font-size: calc( var(--h1-size-min) * 1rem);
  line-height: var(--h1-lh-min); 

  /* JS will scale the next two values dynamically */
  font-stretch: calc( var(--h1-wdth) * 1%);
  font-weight: var(--h1-wght);

Sliding into scale

As we hit the point where we want to start scaling, we add a media query and include the formulas for font-size and line-height. The formula is a doozy, but Tim does a great job explaining his use for it with line-height. I’ve adapted it to use CSS custom properties so there is less variation needed when using the formula in one place or another, or in adjust the breakpoints where you want scaling to stop and start. Here’s the formula in use to scale the font size, and then we’ll break it down just a bit.

h1 {
    calc(( var(--h1-size-min) * 1em ) + 
      ( var(--h1-size-max) - var(--h1-size-min) ) * 
      ((100vw - ( var(--bp-small) * 1em )) / 
      ( var(--bp-xlarge) - var(--bp-small) ))); 

So let’s restate that another way:

“Please scale the font size from [this] to [that], starting when the screen width reaches [here] and stop when it gets wider than [there]”

In this case, as we showed above, we want to scale from an equivalent of 40px to 72px, starting when the viewport reaches 25em, and stop scaling when the viewport is wider than 65em. The magic is where we bring viewport units into the formula. The other parts are fairly straightforward. But browsers can do math that we cannot—so that’s how we get two important outcomes: the value scales smoothly, but because we’ve mixed viewport units with ems, users can still zoom in on the text. When using strictly viewport units for sizing, the text will never scale—creating a huge accessibility issue.

The nice thing is all we have to do is swap out the variables for font-size (or line-height) and we can use exactly the same structure everywhere we want to use it. The only caveat at this point being CSS does not support ending up with a unit-less value. This is why we can only use this technique for things like font-size or line-height. To scale font-weight (which needs just a number) or font-stretch (which must be expressed as a percentage), we need a different solution.

Just a little slide to the left

Until we have browser implementations that allow for unit-less calculations (spec is updated, but not yet implemented. So we’ll use a bit of JavaScript instead. We want to do the same thing the CSS calc() is doing: scale a weight or width from one value to another (up or down) based on viewport size. The reason we want it to work in either direction is to allow for something like the top level heading to get larger and wider as the viewport increases, but at the same time we want the weight to reduce, giving us a larger, lighter the treatment on larger screens. Thankfully, this is where Mandy’s Fluid Weight CodePen comes in handy—so I’ve used that as a starting point to set up our solution. 

Image of large and small screen demo without width or weight scale on the text
There’s nothing inherently wrong with having the width and weight stay the same, but there’s opportunity for improvement when we scale them to best suit the device and context
Now with the scaling of width and weight in place for heading and body copy

Primarily the changes revolve around reading and setting more CSS custom property values for breakpoint settings, minimums, and maximums—and do that for multiple HTML elements. In this case we want to scale the width and weight of h1’s, h2’s, and p’s. Better JS wranglers than I could probably streamline this even more, but it’ll do for now!

First we set up the functions (one main one, one nested inside it), and then the event listeners (on load, and on resize). This ensures we get correct initial values, and keep them correct whenever the viewport changes.

function variableResize() {

  // set up values by reading existing CSS properties
  function scale(varName, minValue, maxValue) {
    // scale the value, and write it back to the CSS property

window.addEventListener("load", variableResize);
window.addEventListener("resize", variableResize);

Inside the main function, first we set up the process by which we can read and save CSS custom property values. Then we read in a few values to set things like breakpoint values, and a series of variable sets for each HTML element that we want to scale. With each set of variables we call the inner function that calculates the new value and saves it back to the appropriate custom property.

function variableResize() {

  // get the computed styles into an object
  let bodyStyles = window.getComputedStyle(document.body);	
  // set up a place to save new values back 
  let root = document.documentElement;
  // Minimum and Maximum viewport size - must be unitless em values
  const maxWindowSize = bodyStyles.getPropertyValue('--bp-xlarge') * 16;
  const minWindowSize = bodyStyles.getPropertyValue('--bp-small') * 16;
  // Get current viewport size
  const windowWidth = window.innerWidth
  // Scale elements and min/max values
  const h1WidthVar = '--h1-wdth';
  const h1MinFontWidth = bodyStyles.getPropertyValue('--h1-wdth-min');
  const h1MaxFontWidth = bodyStyles.getPropertyValue('--h1-wdth-max');
  scale(h1WidthVar, h1MinFontWidth, h1MaxFontWidth);

  const h1WeightVar = '--h1-wght';
  const h1MinFontWeight = bodyStyles.getPropertyValue('--h1-wght-min');
  const h1MaxFontWeight = bodyStyles.getPropertyValue('--h1-wght-max');
  scale(h1WeightVar, h1MinFontWeight, h1MaxFontWeight);

  function scale(varName, minValue, maxValue) {
    // scale the value, and write it back to the CSS property

window.addEventListener("load", variableResize);
window.addEventListener("resize", variableResize);

The other bit of note in the inner function is that we check to see which value is greater: the ‘lower end’ or the ‘upper end’ (based on viewport range), and calculate accordingly. That way we can scale weight down while font size goes up (or whatever else we want to impact. Here’s the code:

function scale(varName, minValue, maxValue) {
  // Make sure min/max are numbers
  minValue = minValue * 1;
  maxValue = maxValue * 1;

  //Scale within a range
  const percent = (windowWidth - minWindowSize) / 
    (maxWindowSize - minWindowSize);
  if (maxValue < minValue) {
    var valueScale = minValue - (percent * (minValue - maxValue));
  } else {
    var valueScale = (percent * (maxValue - minValue)) + minValue;
  // Get the new font width
  const newValue = windowWidth > maxWindowSize
    ? maxValue 
    : windowWidth < minWindowSize 
      ? minValue 
      : valueScale;
  // Set my CSS Custom Property for width to update., newValue);

We’re only using this for width and weight—but don’t forget that you could scale any other value in a similar fashion. Just supply another set of base variable and range values.

So why do it this way

The first thing some might ask is why not just use a modular scale system. There are many articles about them, and a really nice tool built by Scott Kellum and Tim Brown. The problem I have is that the earlier implementations didn’t address screen size—just the relationship between elements. But when those relationships need to factor in another dimension (viewport), they can quickly break down. What we need is a way to scale the whole system, from one set of relationships to another. We need something more robust. 

There are a couple of other solutions out there that might fit the bill: Typetura.js from Scott Kellum and Sal Hernandez, and Textblock from Theo Rosendorf and Glyphic. Typetura in particular is interesting as it has a UI to prototype the typography and save the resulting code. They’re actively working on improving the tool and the UI, so it’s definitely something to watch and try. Both projects rely on JavaScript to work. While this is fine in many circumstances, my goal was to use as little JS as possible, relying on native CSS for as much of the rendering process as possible. This should result in faster, smoother page rendering.

No solution is perfect, but so far this has worked under the widest array of circumstances. As noted before, this does require support of variable fonts (which also coincides with support for CSS custom properties and calculations). It can be set up (as in other demonstrations) with support for static web fonts, but that makes for a harder demo to explain. It also requires that the initial value for font-size and line-height be smaller than the end value. But that does fit with a good number of use cases. It’s maybe not so often that you want to make the type smaller as the screen gets bigger.

Ending with a beginning

By bringing these three technologies and techniques together: variable fonts, CSS custom properties, and calculations—my hope is to help designers and developers familiarize themselves with these capabilities, paving the way for using them in other places and in other ways. One example is the layout offset from last week’s demo, but there are limitless others. I hope you’ll share what you do with it!


Web Type News

Does your organization need a boost?

If your brand voice needs some volume, or your team could use a hand improving font performance—maybe you could use a Type Audit. I work with you and your team to identify how well your site’s voice aligns with your brand, and can show you how to improve how quickly it gets on screen on any size device. Read more about Type Audits and let’s talk!

Upcoming Events

  • Winding up in the Windy City for An Event Apart Chicago on August 26-28
  • Teaching a full-day workshop and giving a talk on variable fonts at ATypI Tokyo on September 4-7
  • Teaching a full-day workshop and giving a talk on variable fonts at Web Unleashed in Toronto on September 12-14
  • Heading to Edinburgh, Scotland, for Finch Front-End to talk about Dynamic Typography and Variable Fonts on September 23-25
If the typeface in this message has Georgia on your mind, you're not seeing the web fonts :( I'm hoping that you're seeing it set in the lovely Roslindale (Display Condensed and Text) designed by the amazing David Jonathan Ross for his You should check it out!
Copyright © 2019 Jason Pamental, All rights reserved.

Forward to a friendSubscribe
Want to change how you receive these emails?
You can update your preferences or unsubscribe from this list.

Email Marketing Powered by Mailchimp