User Experience and Web Fonts or. The Crazy World of Loading Fonts on the Web

“Close-up of pieces of wooden type” by Raphael Schaller on Unsplash

Using custom fonts are nothing new on the web but, it can still be one of those slices of the pie that can slow down your website and up the cost to end users. Especially if you are not mindful, things can get our of hand real quickly as this query I did against the HTTP Archive’s data demonstrates.

With this in mind, we at MDN Web Docs are currently experimenting with lowering the impact of custom fonts on our end users. This includes perceived performance, actual measurable performance and cost to the end user.

The landscape is a little hairy to put it mildly, and we have definitely not stirred up a secret sauce. In the post I want to go over some of the known, and possibly lesser know ways we can tackle using custom fonts on the web.

Note that this post is about how you can best load custom type faces for improved performance and a better user experience. Before you get to this step, you need to take an inventory of the fonts used on your website, how much weight they add to your pages overall, and then make an honest decision on whether you really need all those weights, variants and glyphs the typeface offers.


This is the most widely supported and most common way to include custom web fonts. It does come with a downside though. As Manuel Matuzovic noted in his excellent article:

The good thing about @font-face is that fonts are loaded lazily, but the bad thing about @font-face is that fonts are loaded lazily.

To add to this, consider the following table

That is a lovely demonstration of the unpredictability of how lazy loading with@font-face works across browsers, and adds credence to Manual’s quote from above.

Be that as it may, it is the starting point for getting custom web fonts on your website.


This is a new descriptor added to @font-face as part of the CSS Fonts Module Level 4. It essentially gives us developers some control over the behaviour of the font loading, and display process. You can specify one of five possible values which are, auto, block, swap, fallback, and optional.

Browser support for font-display is pretty good already, if you ignore Internet Explorer and Edge so, which makes it a great candidate for progressive enhancement. This is then the first of these that we are implementing on MDN Web Docs.

I am not going to go into to much detail with regards to each of the five possible values, I would head over to the spec for details but, will briefly talk about a couple of them here.

Both auto and block is essentially the same and should generally be avoided. The only real use case for this, is if rendering the copy in any other font will directly impact usability. The reason? If you specify font-display: block;, any copy that uses that font will be render with “invisible” ink for up to three seconds or longer, before a fallback will be used.

The most useful therefore are swap, fallback, and optional.


This has a zero second block time but, and infinite swap time. This means that any copy that is set to font-display: swap; will immediately be drawn with a fallback typeface, and as soon as the custom font has loaded, the browser will swap the new font face in.

Care should be taken when using this though, as it can create a janky and disruptive experience for the end user, but can useful for a company’s logo type, for example


Now we get to the most useful of the five. This has a very short, around 100ms block time with a roughly three second swap time. This means that if the custom font is not immediately available, the browser will use the fallback type face.

Should the custom font load before the swap time runs out, the custom font will be swapped in. If not, the browser will stick with the fallback font for the lifetime of the current page. This is particularly useful for body copy.


As the name suggest, this is used for copy where the custom type face is a nice to have. This gives you the same block time as fallback, but a zero second swap time. This is like saying, “If the type face is already loaded, use it. If not, Hey browser, I trust you to do what it best for my user so, you decide what to do here.”

The last two are then also the options we are currently experimenting with on MDN Web Docs.

Link type preload

Using font-display is a great step in the right direction but, what if you want to load some of your most critical font faces as early as possible. Giving them the best chance of being loaded by the time the browser starts asking for the font?

There are two standards based ways to do this. In this post I am going to look at the preload specification, but I would encourage you to also have a look at the CSS Font Loading Module.

The first thing to note is that preload is different from resource hints, in that it is a declarative fetch primitive that initiates a fetch, and not merely a hint. What really makes preload shine is that it decouples fetching of the resources, from parsing and execution.

This means that the browser will fetch the resource but, other than a shallow parse of CSS, will not parse, execute, nor apply the resource to the current context. This then provides a mechanism to preload critical resources without blocking the window’s onload event, and without blocking the render process.

A couple of things to note. Other than the preload keyword used for the rel attribute, you need to specify the resource type your are preloading. This is important, as it allows the browser to set the appropriate priority, apply your CSP policy, and set correct Accept header.

The way you specify the resource type is the using the as link-extension target attribute. There are a number of possible values for the as attribute, and in the case of fonts, you guessed it, you would use the value font.

Another very useful attribute you should use, especially with type faces, is the type attribute. As you guessed it, it allows you to specify the MIME-type of the type face you are loading. This allows the browser to determine support, and not preload the type face if it does not support the MIME-type, thus avoiding unnecessary bandwidth usage for your users.

The final attribute to be aware of is the crossorigin attribute. As the name suggests, this attribute will be required if you are loading the type face from a domain other than your own.

With that said though, Addy Osmani wrote an extensive article on preloading and resource hints in Chrome here on Medium, and in that post there is a very important detail you might overlook.

Preloaded fonts without crossorigin will double fetch! Ensure you’re adding a crossorigin attribute when fetching fonts using preload otherwise they will be double downloaded. They’re requested using anonymous mode CORS. This advice applies even if fonts are on the same origin as the page. This is applicable to other anonymous fetches too (e.g XHR by default).

With all of the above in mind, let’s bring it all together and preload a custom type face:

<link rel="preload" href="./fonts/OpenSans-Regular.woff2" as="font" type="font/woff2" crossorigin />

With just that one line of code, you can positively impact the performance, and user experience of your website. This is then the next thing we will be rolling out on MDN Web Docs, and I hope you are as excited as me about the potential for this.

I hope you enjoyed this post, and learned something you can use to improve the performance of your own websites. What other strategies are you employing with regards to custom type faces? Looking forward to your feedback and comments.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Schalk Neethling

Schalk Neethling

I write about mental health, addiction, sober living, living your best life through an active lifestyle and a whole food plant-based diet. Psychedelic curios :)