Styling Fallback Fonts with Sass

One of the pain points of loading fonts asynchronously is writing convoluted CSS for fallback styles. Fortunately, Sass can make it easier to achieve great-looking web typography before a site’s fonts finish loading.

If you’ve ever read a blog post about loading web fonts asynchronously for performance, you’ve probably encountered CSS like this before:

html {
  font-family: 'Arial', sans-serif;
}

html.wf-active {
  font-family: 'Source Sans Pro', 'Arial', sans-serif;;
}

Look familiar? In that example, I’m using the Web Font Loader to load Source Sans Pro via JavaScript. When the fonts finish downloading, the library adds a wf-active class to the html element. Then the font family switches from Arial to Source Sans Pro — or from fallback font to web font.

Unfortunately, most web fonts don’t have ideal system font equivalents. To soften the janky reflow when switching fonts, you’ll need to fine-tune the fallback family’s size and other properties.

Arial is bigger than Source Sans Pro, so in this example, I reduced its font size to 20px to match Source Sans Pro at 21px. To compensate for the decrease in font size, I also increased Arial’s line height.

html {
  font-family: 'Arial', sans-serif;
  font-size: 20px;
  line-height: 1.45;
}

html.wf-active {
  font-family: 'Source Sans Pro', 'Arial', sans-serif;
  font-size: 21px;
  line-height: 1.35;
}

Here, I wrote two selectors: html and html.wf-active. It’s a pretty intuitive for properties inherited from the html element, but relying on a similar approach elsewhere will introduce a couple maintenance problems throughout the CSS.

Let’s go over the issues one by one and I’ll show you ways to use Sass to clean things up.

Nest with the Ampersand Selector

The first thing we can do is use Sass’ ampersand selector to nest the current selector within an ancestor. It’s easier explain with an example:

h1 {
  font-family: 'Arial', sans-serif;
  font-size: 34px;
  line-height: 1.25;

  html.wf-active & {
    font-family: 'Source Sans Pro', 'Arial', sans-serif;
    font-size: 36px;
    line-height: 1.1;
  }
}

The compiled CSS looks like this:

h1 {
  /* Fallback styles */
}

html.wf-active h1 {
  /* Web font styles */
}

See how the h1 comes after html.wf-active? Nesting this way cuts down on a little repetition, but there’s a lot more we can do.

Decrease Specificity

html.wf-active h1 is a compound selector, which makes it difficult to override any of its styles later on in the style sheet. Its specificity is unreasonably high for what should be a base style.

We can address that problem by flipping the selector so it affects specificity only when web fonts haven’t loaded. The not() pseudo-class lets us do exactly that.

h1 {
  font-family: 'Source Sans Pro', 'Arial', serif;
  font-size: 36px;
  line-height: 1.1;

  html:not(.wf-active) & {
    font-family: 'Arial', serif;
    font-size: 34px;
    line-height: 1.25;
  }
}

Now when web fonts finish loading and the wf-active class gets applied, html:not(.wf-active) h1 styles are ignored and the h1 specificity stays nice and low.

Put It in a Mixin

I don’t know about you, but I don’t want to write html:not(.wf-loaded) & for every fallback font style throughout my code. It’s easy to forget and tedious to write.

So let’s create a mixin to speed things up. The syntax for this one is simple. Name yours whatever you like, put the ampersand selector in there, and specify where the content goes.

@mixin fontless {
  html:not(.wf-active) & {
    @content;
  }
}

Now use that mixin to apply fallback font styles with ease.

h1 {
  font-family: 'Source Sans Pro', 'Arial', sans-serif;
  font-size: 36px;
  line-height: 1.1;

  @include fontless {
    font-family: 'Arial', sans-serif;
    font-size: 34px;
    line-height: 1.25;
  }
}

If you want to use the same mixin on the html element, you’ll need to add a conditional to make sure it doesn’t nest inside itself.

@mixin fontless {
  @if is-superselector('html', &) {
    &:not(.wf-active) {
      @content;
    }
  }
  @else {
    html:not(.wf-active) & {
      @content;
    }
  }
}

And then it works on html. Kablamo.

html {
  font-family: 'Source Sans Pro', 'Arial', sans-serif;
  font-size: 21px;
  line-height: 1.35;

  @include fontless {
    font-family: 'Arial', sans-serif;
    font-size: 20px;
    line-height: 1.45;
  }
}

Okay, we’re done here. Who needs conclusions on front-end blog posts? You learned a thing — go have fun jank busting fallback fonts. Or send me a tweet if you liked the article or have any questions. That’s cool, too.