Content is the most important thing on your site, but typography is how you dress it up. The clothes you put on every morning express purpose for your day, and that’s what typography does for your content. Assuming you’re not just using emojis to express semantic meaning on your sites you’re using typography, and that same typography is in the equation of what’s making your users feel like using your site in the first place.

If you’re like me you you want that type to make your users feel good. You want your type to be beautiful.

“That’s all well and good,” you say, “But what does it mean to have beautiful web typography? What are the steps from getting from knowing relatively little about typography to making typefaces beautiful?”

It would also be pretty valid to ask, “Fancy web type means HTTP requests, and additional load time, and potentially poor user experience—how can I be sure that I’m making all of this performant?”

To which I say, don’t worry. That’s why you’re reading this.

What is Beautiful Web Typography

Typography doesn’t just exist on the web. If you want beautiful web typography, you need to get beautiful typography first. As with most things in life, having a little history doesn’t hurt.

Gutenberg and his printing press mark the birth of modern typography but written language stretches way further into the past than the 15th century. Before Gutenberg, religious men and scholars all over the world were trying to pen history by hand. Calligraphy, the art typography sought to modernize, is really old.

Old like hieroglyphics Batman is old

Have you ever done any UX design?

When you’re trying to create a new website or new app, you draw from patterns you’ve already seen in other places that work well. This is because choosing patterns users are familiar with helps them acclimate to your product faster.

Typography over the years did the exact same thing—it copied from predecessors. Those religious men penning history created letters and alphabets and words and the way those things looked on the page. When that technical revolution hits, it copies what these men had been doing, and even now typography draws inspiration from the way we write on paper. That technical revolution? That’s Gutenberg. That’s where typography begins.

Typography copying the way we write and technical revolution means three patterns emerge:

  1. Typography and what we as readers find legible change and morph together, and are influenced by how we’re reading—be it by book, newspaper, or webpage.
  2. Typography is also influenced by the way people write by hand, and borrows the solutions people find when writing by hand when it needs them.
  3. Typography nonetheless becomes fundamentally different from traditional calligraphy because of the ways printing presses work. Instead of letters being coupled closely together, printing presses force each letter to be in an individual metal box called a slug.

I found this great tweet the other day:

If we think of this phenomenon as something that happened because typography was copying a previously established, rich art, it makes sense. All those extra things you don’t see in a font are problems that people, when they’re writing by hand, just solve with a pen. But when you need to fit everything into a keyboard, it becomes a little more complicated.

When we look at all of the glyphs and characters that a font has, when we look at all of the features a font might have, you can understand their relevance and the functions they serve a reader by keeping those three facts in mind. Typography needed to create glyphs, or characters, for all these permutations that we create when we write—and it was challenging, because printing presses meant you needed to have every single character in a little metal box.

Before we get to what’s in all of those individual metal slugs, let’s brush up on our 15th century European history and talk a little bit about Johannes Gutenberg.

Why did the printing press change written word so much?

To understand how we got from calligraphy to typography, it’s helpful to understand how a printing press works. To print books on a printing press, you set small metal bars called slugs, each with a letter or a combination of letters, in rows. The slugs need to be set up in this way to be moveable and interchangeable.

Written word, before the printing press, was able to fix awkward spacing between letters. Acronyms in bodies of text flowed. If you were talented, adding flourishes to your work was simple.

Printing presses’ needs were very different. Typography needed ways to solve awkward spacing, and build flourishes, and have number fractions—all of the things that had been solved and were easy to write, but not easy to typeset. Ellen Lupton in her book Thinking with Type puts this really elegantly:

“The first typefaces were directly models on forms of calligraphy. Typefaces, however, are not bodily gestures—they are manufactured images design for infinite repetition.”

— Ellen Lupton, Thinking with Type

To solve these issues, type designers had to create all the glyphs that you can find in fonts today:

Slugs meant that as a type designer you had to choose and find important combinations of letters. You had to give decorative glyphs for people printing books. You needed to create small caps. You needed to create often-used fractions.

If it wasn’t obvious this is the direction we’re heading with this talk: these things make your content shine. Using these beautiful little characters that were specially created to solve specific kinds of problems makes your text more legible and makes the details more beautiful.

The need for these characters didn’t go away when we switched to digital typography, which is why the ability to turn these features on and off in CSS exists. When we use typography on the web, we have a myriad of characters and glyphs at our disposal, all of which can be turned on and off with Opentype CSS flags.

There are over 100 of them, so I’m not going to go through every single one, but we can at least learn a few some common ones:

There is kerning, which is the adjustment between letters so spacing looks good. Type designers will put a lot of work into figuring out spacing between your letters, so it makes sense to turn this on to auto, which ensures the browser will choose the most performant kerning option on by default.

There are ligatures, which fix awkward letter combinations. When type gets really small, spacing gets awkward between certain letter combinations. Ligatures fix that. These are normally split into normal and discretionary ligatures, discretionary meaning, “turn on if you want to be extra fancy.” This is a good option for headers and pullquotes.

Next there are small caps, which make make abbreviations look good. This is because they don’t interrupt the flow of a sentence by staying at the same height as the lowercase letters.

Old Style Numerals give you numbers that go below the baseline, which look more similar to the way you might write out numbers by hand, which has the added benefit of making them more legible in certain scenarios.

Swashes allow you to get really fancy with your typography, and are great for fancy headers and article titles.

Any of these one changes, in the grand scheme of building an entire website, is relatively small; but together they have a net gain much larger than their individual parts. They make type more legible, and thus, make your site more pleasant to be on.

These things are pretty easy to turn on; they’re ultimately just CSS properties (I’ve left them all unprefixed for brevity):

font-feature-settings: "kern", "liga", "clig", "calt";

The challenging part is knowing what your typeface supports, which you can figure out with a variety of methods.


If you’re serving up the font from Typekit, it will list those features. Once you’ve created a kit on Typekit you can see the OpenType features the typeface supports by clicking the question mark next to “OpenType Features”. Unfortunately, you already need to be pretty familiar with the OpenType syntax to know what these mean.

For Google Fonts, and fonts you have locally on your machine, you’ll need to do a little more digging to figure out what features the typeface supports. In these instances there are OpenType sandboxes.

I like this one because it makes it pretty easy to just turn on and off the different features and it spits out the relevant CSS along the bottom of the page. If you click an OpenType feature and nothing happens, that particular feature is probably just not supported by that typeface.

The last challenge is discovering additional glyphs that a font file might contain, since we know from this tweet that sometimes there are additional characters and glyphs that you can’t access without key commands or help from some sort of visual editing software. To find these, you have two options:

  1. Looking at all of the glyphs using Font Book/Character Map
  2. Or, if you have a spare $600, using Adobe InDesign

If you want to see all of the glyphs your typeface has and you have it locally on your computer, you can use Font Book if you’re on a Mac or Character Map if you’re on Windows. In Font Book you can toggle this option in the upper left to see all of the characters in a typeface.

Your other option, if you have a license for Adobe’s Cloud Services, is to use Adobe InDesign, which has a glyphs panel where you can see all of the discretionary ligatures and glyphs, along with their unicode mapping.

The workflow for playing with a new typeface I’d like to use is typically to use an OpenType sandbox to discover the CSS features the typeface supports, and then to use Font Book to discover glyphs I might want to use in my designs. For example, if you’re on Mac, the system font, San Francisco, has a lots of little fun wingdings, including nicer punctuation to replace “dumb” or “straight” quotes:

   ❛ ❜ ❞❝

Font Book also is a way, using the information panel, to see what languages a typeface supports, which is good information if you’re building a site that needs to support different character sets, or languages with lots of accents.

Let's talk performance

At this point, you’ve learned about type features. You know where to find them, how to turn them on. You might even have already formed some opinions on what are the things you want in a typeface and have some opinions on what you think looks good, either because of this talk or from things you’ve read on your own. At this point, we’re going to pretend you’ve chosen a typeface. And now you want to load it and use it on your site.

But what about performance?

Obviously as web developers we care about performance. You don’t want a large webfont slowing down the loadtime of your site. At the very beginning of this talk you may remember hearing me say:

Content is king

I want you to keep it in mind as we go through these performance tips as it’s the driving philosophy behind what we’re doing. I think that Chris Manning put it best in his article on font loading techniques:

“…Readable content trumps custom fonts.”

— Chris Manning, The @font-face Dilemma

If you’ve ever loaded in a webfont from Google fonts you probably copy and pasted their suggested import code, which looks something like this:

@import '';

Or this:

<link href="" rel="stylesheet">

These methods get the job done but we can improve on them. For one, they can cause a flash of unstyled text:

Even worse, they can cause a flash of invisible text, which means you can’t even read the content before it’s all loaded:

If you’re on poor wifi and you’re just trying to read something, it’s safe to say:

A flash of unstyled text is a better experience than not being able to read something at all. Thus, when we’re working with web type, we want to allow for flashes of unstyled text to happen, and we want to then mitigate and minimize their effects.

Preventing the FOIT

The first thing we need to do is prevent the flash of invisible text. And the way you do this is with… well, websafe or system fonts.

To do this, we’re going to declare a websafe font first in our stack, and then use JavaScript to load in our webfont once it’s ready to use.

For this demos, I’m going to be using a typeface named Calendas Plus.

@font-face {
      font-family: 'Calendas Plus'; 
      src: url('/css/type/calendas-plus-webfont.woff2') format('woff2'), 
           url('/css/type/calendas-plus-webfont.woff') format('woff'),
           url('/css/type/calendas-plus-webfont.ttf') format('truetype');
      font-weight: 300;
      font-style: normal;

  body {
    font-family: serif;

  body.fonts-loaded {
    font-family: 'Calendas Plus', serif;

Here, we’re declaring our webfont font stack first on the body, and then our desired font stack in a class.

var fontFamilies = {
  'Calendas Plus': [
      weight: 400
      weight: 700

var fontObservers = [];

Object.keys(fontFamilies).forEach(function(family) {
  fontObservers.push(fontFamilies[family].map(function(config) {
    return new FontFaceObserver(family, config).check()

  .then(function() {
  }, function() {
    console.log('Fonts not available');

For the JavaScript, we’re using a small library produced by Bram Stein, who works at typekit named Font Face Observer.

In this code, we start an object with the different font families we want to load. Here we have one typeface with two different weights, which we can append to later with different families.

We create a new font face observer for each family in the object; this will return a promise that is resolved when the font has loaded, or rejected if the font fails. In the promise below we handle the two use cases—in the event of the promise being fulfilled, we add the ‘fonts-loaded’ class to the body that will trigger the typeface from our CSS and then apply that Calendas Plus typeface that looks so nice. Voilà!

Minimizing the FOUT

There’s still work to be done though to minimize our new problem: the flash of unstyled text that the user sees between the initial render of our websafe font and our webfont that we’ve chosen.

To do this, we’re going to compare the font we have with the font we want when we’re choosing our stack. This trick comes from Tim Brown, who explains the philosophy by pulling a quote from Jason Santa Maria’s book On Web Typography:

“The thing I try to avoid most in my designs is not FOUT but a jarring shift in the layout when a webfont finishes loading. This shift is usually due to sizing discrepancies between your layout and system fonts, and your webfont.”

— Jason Santa Maria, On Web Typography

The way that Tim Brown handles this discrepancy is by matching his fallback font and his intended webfont’s x-heights and widths—a typefaces’ x-height being the height of the lower case letters, and the width being the literal width of the letters when laid out in a line:

In these examples I’m using Calendas Plus and Georgia, both set at 36 points. When we overlay them we see how Georgia is both taller and wider than Calendas Plus is:

By bumping down Georgia to 35 points, we get a much closer match between the two that isn’t going to cause as dramatic a shift in the layout as a user begins to read because of similar height heights, and closer widths:

As Tim Brown puts it,

“The style doesn’t matter so much, it’s that it has to flow the same way.”

— Tim Brown, Twitter

@font-face {
    font-family: 'Calendas Plus'; 
    src: url('/css/type/calendas-plus-webfont.woff2') format('woff2'), 
         url('/css/type/calendas-plus-webfont.woff') format('woff'),
         url('/css/type/calendas-plus-webfont.ttf') format('truetype');
    font-weight: 300;
    font-style: normal;

body {
  font-family: Georgia, serif;
  font-size: 35pt;

body.fonts-loaded {
  font-family: 'Calendas Plus', serif;
  font-sized: 36pt;

This means that we need to modify our css a little bit from before.

Instead of just specifying any old serif, we want to specify Georgia, since it was a serif that closely matched Calendas Plus. Secondly, we want to set Georgia, the font that loads first, at 35 points, and then Calendas Plus, the font that loads second, at 36 points, to avoid those layout shifts.

But wait! We can do even more!

If you’re loading in a typeface from Typekit or from Google fonts, this should get you pretty far. Already, you know how to circumvent Flashes of Invisible Text with a lazyload so that your users can read their text and get a nice webfont at that, and we know how to make sure that this load isn’t disruptive to the reading experience. But we can still improve on this!

Zach Leatherman from Filament Group shows us that we can get even more granular with our lazyloading, splitting up the normal (or roman), italic, and bold versions of our typeface, using the browsers’ faster, built-in algorithms to italicize and bold our typeface until we can lazyload the proper italic and bold versions of our typeface. He calls this FOFT, or

Flash of Faux Text

The way that Zach handles this is using a fontfaceonload polyfill he’s created, which executes a callback when a webfont loads. To use it, we load the fontfaceonload library and set up our loads to happen in “stages”—Stage 1 being the roman font family loading, and stage two being the bold and italic styles of the font family loading.

FontFaceOnload( "ProximaNovaRegular", {
  success: function() {
    var docEl = document.documentElement;
    // Stage 1 Complete
    // FOFT engaged
    docEl.className += " proxima-nova-loaded";

    var counter = 0;
    var success = function() {
      if( counter === 2 ) { // number of additional font faces we're loading in
        // Stage 2 Complete
        // All Fonts Loaded
        docEl.className += " proxima-nova-b-loaded";

    FontFaceOnload( "ProximaNovaBold", {
      weight: 700,
      success: success
    FontFaceOnload( "ProximaNovaLight", {
      weight: 300,
      success: success

We’re going to use Proxima Nova for these examples, since Proxima Nova has a lot of different weights that would be fabulous to load in over time:

Here we’re loading the roman version of our typeface first. On completion for that variant, we have two more checks for the bold and light versions of our typeface. If these succeed, we add another success class to the top element on our page so our css file knows to apply the correct font file.

All font variants need to be aliased to different names to avoid the FOIT
@font-face {
    font-family: ProximaNovaBold;
    src: url('proxima_nova_bold-webfont.woff2') format('woff2'),
         url('proxima_nova_bold-webfont.woff') format('woff');
    font-weight: 700;
    font-style: normal;

@font-face {
    font-family: ProximaNovaLight;
    src: url('proxima_nova_light-webfont.woff2') format('woff2'),
         url('proxima_nova_light-webfont.woff') format('woff');
    font-weight: 300;
    font-style: normal;

@font-face {
    font-family: ProximaNovaRegular;
    src: url('proxima_nova_reg-webfont.woff2') format('woff2'),
         url('proxima_nova_reg-webfont.woff') format('woff');
    font-weight: 400;
    font-style: normal;

html {
    font-family: -apple-system, BlinkMacSystemFont, Helvetica, sans-serif;

/* Stage 1 */
html.proxima-nova-loaded {
    font-family: ProximaNovaRegular, sans-serif;

 * Stage 2
 * The rest of this CSS is to work around
 * the separate font-family limitation
 * described below.

.proxima-nova-b-loaded h1,
.proxima-nova-b-loaded h2,
.proxima-nova-b-loaded h3,
.proxima-nova-b-loaded h4,
.proxima-nova-b-loaded h5,
.proxima-nova-b-loaded strong {
    font-family: ProximaNovaBold;
.proxima-nova-b-loaded em {
  font-family: ProximaNovaLight;
  font-style: italic;
.proxima-nova-b-loaded strong em,
.proxima-nova-b-loaded strong blockquote,
.proxima-nova-b-loaded em strong {
  font-family: ProximaNovaBold;
  font-style: italic;

First we load in our webfont using an @font-face declaration. We’re doing this three times over, one for each weight of the font we want to load in, because grouping them together will cause the dreaded FOIT we’re trying to avoid if we load them in the way we’re doing with our javascript. Browsers are smart enough to not download a @font-face until the class exists in the DOM tree, so we don’t need to worry about these being downloaded and slowing down render.

First, we declare our websafe fonts that most closely match our desired font, as we did before. The closest I could find to Proxima Nova was the Mac system font San Francisco, but system fonts on Windows and Linux didn’t match as closely, so instead of having the traditional system fallbacks of Segoe UI and Tahoma for Windows and Linux systems, I’m next just setting Helvetica.

Next we set a loaded class named .proxima-nova-loaded, where we set our desired webfont. All still things you’ve seen before.

Next we start seeing some new code—a .proxima-nova-b-loaded class, the class our JavaScript sets when the italic and bold versions of our typeface have loaded on the element. We have to refine where in what instances we want italics and bolds to load since we’ve stripped them out previously—hence resetting italics on em elements and resetting bolds on strongs and h*s to use the webfont’s true bold and italics instead of the browsers’.

All of these strategies have the potential to timeout after a period set by the browser, and that’s okay—at least your content loads.

Wrapping Up

There’s a talk by Kenneth Ormandy that really wonderful on web typography, and he tells a story about the printing press, but 100 years after Gutenberg and the story we covered.

100 years after the printing press was created, a man named Pierre Haultin is trying to recreate Gutenberg’s greatest accomplishment: printing the Bible. But Haultin’s reasons for doing so are a little different than Gutenberg’s: Gutenberg wanted to print the Bible. Haultin wants to print like… the mini-Bible. That’s because Haultin is a Protestant living in very Catholic France where he is unable to lug around a big ol’ Protestant Bible that he might get caught with.

In the end, do you wanna know what his solution was?

It was to make a better printing press!

It feels really obvious once you say it—of course it was to make a better printing press—but I think it extrapolates to our challenges using web fonts. As of last week the HTTP Archive had a chart saying that 67% of websites on the web load in a custom font of some sort, showing that we want to use custom fonts. We just haven’t figured out the efficiency bit yet. We’re still lugging around oversized, Catholic Bibles, and we need to figure out how to downsize them. But that isn’t to say that things won’t change, and that we can’t build better printing presses one day.

I think that as developers, we sometimes can over-execute on our ability to do our jobs. We optimize everything, without thinking about trade-offs and the things we lose by optimizing everything. Instead of letting performance dictate design, can we build great designs that are performant? How can we make technology support the kinds of designs that benefit our users?

I don’t have answers for these questions, they’re open-ended. I think they’re not even just questions for us in this room but questions for everyone who builds websites and cares about their users. We’ve decided as an industry, based on those HTTP Archive numbers, that we want webfonts. Now, how can we make better printing presses?


If you want to learn more about Type, I highly recommend Ellen Lupton’s Thinking with Type. If you like to sweat the small stuff, this is the book for you.

If you want to make working with OpenType features easier, I recommend Kenneth Ormandy’s Utility Opentype and Normalize-OpenType projects, which both work to make working with OpenType easier.

If you want to learn more about progressive typeloading techniques, Zach Leatherman’s research is beyond fabulous. He has ideas for how to get even more performant with Data-URI techniques that I didn’t cover here, so that’s also worth looking at.

Update, 04/2017:

Monica Dinculescu made a tool based on this article for matching fallback fonts and loaded webfonts. So flattered I could die 😭




Types Used

Give me your thoughts on Twitter.