A Comprehensive Guide to Font Loading Strategies
This guide is not intended for use with font icons, which have different loading priorities and use cases. Also, SVG is probably a better long term choice.
Updated July 27, 2017 with new information on font-display
.
Updated August 14, 2017 with a link to a glossary page.
Jump to:
- Unceremonious @font-face
font-display
preload
- Don’t use web fonts
- Inline Data URI
- Asynchronous Data URI
- FOUT with a Class
- FOFT, or FOUT with Two Stage Render
- Critical FOFT
- Critical FOFT with Data URI
- Critical FOFT with
preload
Glossary
If you run into a term that you don’t know, please consult the glossary of web font loading terms I’ve prepared. If a term is missing in the glossary, please let me know on Twitter @zachleat or in the comments.
Quick Guide
If you’re looking for a specific approach, I’ve prepared some handy links that will take you to the section you need. Let’s say you want an approach that:
-
is the most well rounded approach that will be good enough for most use cases: FOUT with a Class.
-
is the easiest possible thing to implement: I’ve learned a lot about web fonts and at time of writing this article the current browser support is lacking for the easiest methods for effective/robust web font implementation. It is with that in mind that I will admit—if you’re looking for the easy way out already, you should consider not using web fonts. If you don’t know what web fonts are doing to improve your design, they may not be right for you. Don’t get me wrong, web fonts are great. But educate yourself on the benefit first. (In Defense of Web Fonts, The Value of a Web Font by Robin Rendle is a good start. If you have others, please leave a comment below!)
-
is the best performance-oriented approach: Use one of the Critical FOFT approaches. Personally, at time of writing my preference is Critical FOFT with Data URI but will shift toward Critical FOFT with
preload
as browser support forpreload
increases. -
will work well with a large number of web fonts: If you’re web font obsessed (anything more than 4 or 5 web fonts or a total file size of more than 100KB) this one is kind of tricky. I’d first recommend trying to pare your web font usage down, but if that isn’t possible stick with a standard FOFT, or FOUT with Two Stage Render approach. Use separate FOFT approaches for each typeface (grouping of roman, bold, italic, et cetera).
-
will work with my existing cloud/web font hosting solution: FOFT approaches generally require self hosting, so stick with the tried and true FOUT with a Class approach.
Criteria
- Ease of Implementation: sometimes, simple is what makes the deadline.
- Rendering Performance: FOUT is a feature that will allow you to render fallback fonts immediately and render the web font when it loads. We can take additional steps to reduce amount of time a fallback font is shown and reduce the impact of FOUT or even eliminate it altogether.
- Scalability: some font loading approaches encourage serial loading of web fonts. We want the requests to happen in parallel. We’ll evaluate how well each approach works with a growing web font budget.
- Future Friendly: will it require additional research and maintenance if a new font format comes out or will it be easily adaptable?
- Browser Support: is it sufficient to work with a wide enough base to meet most projects’ browser support requirements?
- Flexibility: does the approach easily facilitate grouping web font requests and their repaints and reflows? We want control over which fonts load and when.
- Robustness: What happens if a web font request hangs? Will the text be readable or will the web font be a single point of failure (SPOF)?
- Hosting: does the approach require self hosting or is it adaptable to work with various web font loaders provided by cloud providers/font foundries?
- Subsetting: some font licenses don’t allow subsetting. Some approaches below require subsetting for optimal performance.
Unceremonious @font-face
Throw a naked @font-face block on your page and hope for the best. This is the default approach recommended by Google Fonts.
Pros
- Very simple: add a CSS
@font-face
block with WOFF and WOFF2 formats (maybe even an OpenType format too, if you want better Android < 4.4 support—Compare WOFF with TTF/OTF on Can I Use). - Very future friendly: this is the default web font behavior. You’re in the web font mainstream here. Adding additional font formats is as simple as including another URL in your
@font-face
comma separatedsrc
attribute. - Good rendering performance in Internet Explorer and Edge: no FOIT, no hidden or invisible text. I fully support this by-design decision made my Microsoft.
- Does not require modification of the fonts (through subsetting or otherwise). Very license friendly.
Cons
- Bad rendering performance everywhere else: Maximum three second FOIT in most modern browsers, switches to FOUT if load takes longer. While requests may finish earlier, we know how unreliable the network can be—three seconds is a long time for invisible unreadable content.
- Not very robust, yet: Some WebKits have no maximum FOIT timeout (although WebKit has very recently fixed this and I believe it will be included with Safari version 10), which means web font requests may be a single point of failure for your content (if the request hangs, content will never display).
- No easy way to group requests or repaints together. Each web font will incur a separate repaint/reflow step and its own FOIT/FOUT timeouts. This can create undesirable situations like the Mitt Romney Web Font Problem.
Verdict: Do not use.
font-display
Add a new font-display: swap
descriptor to your @font-face
block to opt-in to FOUT on browsers that support it. Optionally, font-display: fallback
or font-display: optional
can be used if you consider web fonts to be unnecessary to the design. At time of writing, this feature is not available in any stable web browsers. Update: on July 25, 2017 Chrome 60 was released on the Chrome stable release channel which includes support for font-display
.
- Demo:
font-display
: note browser support for this is very limited. - Read more:
font-display
Specification
Pros
- Very Simple: Only a single CSS descriptor added to your
@font-face
block. - Good rendering performance: if this approach had ubiquitous browser support, this would give us FOUT without any JavaScript. A CSS-only approach would be ideal.
- Super future friendly: is orthogonal to web font formats. No other changes are required if you add new formats to your stack.
- Very robust: a FOUT approach will show your fallback text in supported browsers even if the web font request hangs. Even better—your web fonts are not dependent on a JavaScript polyfill—which means if the JavaScript fails, users are still eligible for the web fonts.
- Does not require modification of the fonts (through subsetting or otherwise). Very license friendly.
Cons
- Only available on Chrome (version 60+ on Desktop and Android, see Chrome Platform Status). In progress and behind a flag on Firefox Platform Status but not yet documented at all on Edge Platform Status. Until support is ubiquitous across A-grade browsers, developers will need to pair this with a JavaScript approach.
- In fact, pairing this with a JavaScript approach doesn’t really buy you all that much on an empty-cache load, given that the JavaScript FOUT approaches documented on this page usually require modification of your CSS to avoid using any web fonts prior to font loading classes added by JavaScript. When the two approaches are paired together and the JavaScript fails you won’t get web fonts even though
font-display
is a CSS-only approach. It’ll help for repeat view optimizations though. - Limited flexibility: No way to group requests or repaints. This isn’t as bad as it sounds—if you FOUT everything you’ll avoid the Mitt Romney Web Font problem but grouping can be useful for other reasons—we’ll go into that later.
- ~Hosting: No control of this property on any known web font host. It’s not included in the Google Fonts CSS, for example. This will probably change when browser support improves.~ Update: in May 2019 Google Fonts added the ability to set a
font-display
property, related blog post can be read here.
Verdict: Definitely add it to your @font-face
blocks, but by itself it’s not sufficient.
Preload
Add <link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
to fetch your font sooner. Pairs nicely with an unceremonious @font-face
block and feel free to also throw in the font-display
descriptor as well for bonus points.
- Demo:
preload
- Read more: Preload: What is it Good For?
- Read more: The Web Fonts: Preloaded (the first half)
Keep in mind: The pros and cons for this approach are heavily dependent on the font loading strategy you pair it with, whether it be Unceremonious @font-face
or font-display
.
Pros
- Super easy to implement, one
<link>
and you’re off. - Better rendering performance than a
@font-face
block. Web fonts are requested higher up in the waterfall. - Future friendly if you use the
type
attribute to specify the font format. At this point it’s still possible (although it looks unlikely) that a web browser will implement preload before WOFF2 for example, and without this attribute you could be looking at a wasted request. So, make sure you includetype
. - Does not require modification of the fonts (through subsetting or otherwise). Very license friendly.
Cons
- Scalability: The more you preload, the more you can block initial render (note data for this comparison was gathered on a site that was using Critical CSS). Try to limit usage to the most important one or two web fonts.
- Limited browser support—only Blink support right now, but more coming soon.
- Flexibility: no way to group repaints/reflows.
- You probably wouldn’t be able to use this with a third party host. You’d need to know at markup render the URL of the web font you’re requesting. Google Fonts, for example, generates these in the CSS request you make to their CDN.
Verdict: Not sufficient by itself.
Don’t use Web Fonts
I won’t go into this approach too much because, well, it isn’t technically a font loading strategy. But I will say that it’s better than using web fonts incorrectly. You are missing out on many new typographic features and improvements in readability that a web font can give you, but it is your option to opt-out.
- Demo: Don’t use Web Fonts: yeah, I included this. The title says comprehensive, remember?
Pros
- Not sure it could be simpler: just use
font-family
without@font-face
. - Near instant rendering performance: No worries about FOUT or FOIT.
Cons
- Limited availability. Very few system fonts are available cross platform. Check fontfamily.io to see if a system font has acceptable browser support for your needs.
Verdict: Sure, I guess, but I wouldn’t be excited about it.
Inline Data URI
There are typically two kinds of inlining covered by this method: in a blocking <link rel="stylesheet">
request or in a <style>
element in the server rendered markup. Both alibaba.com (two web fonts embedded in a blocking CSS request) and medium.com (seven web fonts) use this approach.
- Demo: Inline Data URI
- Read more: Web Font Anti-Pattern: Data URIs (the first half)
Pros
- Seemingly great rendering performance: this approach has no FOUT or FOIT. This is a big deal.
- Flexibility: Don’t need to worry about grouping repaints/reflows—this approach has no FOUT or FOIT.
- Robustness: inlining puts all your eggs into your initial server request basket.
Cons
- A catch with rendering performance: while this approach doesn’t FOUT, it can significantly delay initial render time. On the other hand it will render “finished.” But keep in mind that even a single WOFF2 web font is probably going to be around 10KB—15KB and inlining just one as a Data URI will likely take you over the HTTP/1 recommendation of only having 14KB or less in the critical rendering path.
- Browser support: Doesn’t take advantage of the comma separated format list in
@font-face
blocks: this approach only embeds one format type. Usually in the wild this has meant WOFF, so using this method forces you to choose between ubiquity (WOFF) and much narrower user agent support but smaller file sizes (WOFF2). - Bad scalability: Requests don’t happen in parallel. They load serially.
- Self hosting: Required, of course.
Verdict: Only use this method if you really despise FOUT—I wouldn’t recommend it.
Asynchronous Data URI Stylesheet
Use a tool like loadCSS
to fetch a stylesheet with all of the fonts embedded as Data URIs. Often you’ll see this coupled with a localStorage method of storing the stylesheet on the user agent for repeat views.
- Demo: Asynchronous Data URI Stylesheet
- Read more: How we use web fonts responsibly, or, avoiding a @font-face-palm on the Filament Group Lab
Pros
- Rendering performance: Mostly eliminates FOIT (see note in the Cons)
- Flexibility: Easy to group requests into a single repaint (put multiple Data URIs in one stylesheet).
- Ease: Does not require any additional CSS changes to use. This is a big benefit. However, implementation isn’t all candy and roses.
- Robust: If the asynchronous request fails, fallback text continues to be shown.
Cons
- Rendering performance: Has a very noticeable, but short FOIT while the stylesheet and Data URIs are being parsed. It’s quite distracting. I see this method often enough that I can recognize the approach without looking into the source code.
- Flexibility and Scalability: Grouped requests and repaints are coupled together. If you group multiple Data URIs together (which will cause loading to occur in serial and not in parallel), they will repaint together. With this method, you can’t load in parallel and group your repaints.
- Not maintenance friendly. Requires you to have your own method to determine font format support. Your JavaScript loader will need to determine which font format is supported (WOFF2 or WOFF) before fetching the Data URI stylesheet. This means if a new font format comes out, you’ll need to develop a feature test for it.
- Browser support: You can bypass the maintenance of the loader step and hard-code to WOFF2 or WOFF but this will either incur larger than necessary or potentially throwaway requests (the same drawback we talked about for Inline Data URIs).
- Self Hosting: Required.
Verdict: It’s OK but we can do better.
FOUT with a Class
Use the CSS Font Loading API with a polyfill to detect when a specific font has loaded and only apply that web font in your CSS after it has loaded successfully. Usually this means toggling a class on your <html>
element. Use with SASS or LESS mixins for easier maintenance.
- Demo: FOUT with a Class (includes sessionStorage trick for repeat view optimization)
- Read more: Better @font-face with Font Load Events on Dev.Opera and Font Loading Revisited with Font Events on the Filament Group Lab
Pros
- Rendering performance: Eliminates FOIT. This method is tried and tested. It’s one of the approaches recommended by TypeKit.
- Flexibility: Easy to group requests into a single repaint (use one class for multiple web font loads)
- Scalability: Requests happen in parallel
- Robust: if the request fails, fallback text is still shown.
- Hosting: Works independent of font loader (easy to implement with third party hosts or with existing
@font-face
blocks) - Great browser support, polyfills typically work everywhere that web fonts are supported.
- Future friendly: polyfills aren’t coupled to font formats and should work with existing
@font-face
blocks. That means when a new format comes out, you can just change your@font-face
as normal. - Does not require modification of the fonts (through subsetting or otherwise). Very license friendly.
Cons
- Requires strict maintenance/control of your CSS. A single use of a web font font-family in your CSS without the guarding
loaded
class will likely trigger a FOIT. - Typically requires you to hard code which web fonts you want to load on the page. This can mean that you end up loading more web font content than a page needs. Remember that with unceremonious
@font-face
usage, newer browsers only download web fonts that are actually used on your page. This is given to you for free. This is why the New York Times can get away with 100 different@font-face
blocks on their home page—the browser only downloads a fraction of those. With this approach, you must tell the browser which fonts to download independent of usage.
Verdict: This is the baseline standard. This will work for most use cases.
FOFT, or FOUT with Two Stage Render
This approach builds on the FOUT with a Class method and is useful when you’re loading multiple weights or styles of the same typeface, e.g. roman, bold, italic, bold italic, book, heavy, and others. We split those web fonts into two stages: the roman first, which will then also immediately render faux-bold and faux-italic content (using font synthesis) while the real web fonts for heavier weights and alternative styles are loading.
- Demo: FOFT, or FOUT with Two Stage Render (includes sessionStorage trick for repeat view optimization)
- Compare source code for FOFT against FOUT with a Class
- Read more: Flash of Faux Text—Still More on Font Loading
Pros
- All the existing Pros of the FOUT with a Class approach.
- Rendering performance: Greatly reduces the amount of content jumping that occurs when the web font has loaded. Given that we divide our web font loads into two stages, this allows the first stage (the roman font—the one that will incur the most reflow) much quicker than if we had grouped all our fonts together into a single repaint.
Cons
- All the existing Cons of the FOUT with a Class approach.
- Some designers are highly allergic to font synthesis. Objectively, synthesized variations are less useful than their real counterparts but that isn’t a fair comparison. Keeping in mind that the synthesized versions are only a temporary placeholder, the question we need to ask is: are they more or less useful than the fallback font? More. The answer is more.
Verdict: Great for those interested in extra performance but can’t subset with Critical FOFT.
Critical FOFT
The only difference between this method and standard FOFT approach is that instead of the full roman web font in the first stage, we use a subset roman web font (usually only containing A-Z and optionally 0-9 and/or punctuation). The full roman web font is instead loaded in the second stage with the other weights and styles.
- Demo: Critical FOFT (includes sessionStorage trick for repeat view optimization)
- Compare source code for Critical FOFT against FOFT
- Read more: Critical Web Fonts
Pros
- All the existing Pros of the FOFT approach
- Rendering performance: The first stage loads even faster (more noticeable on slower connections) further minimizing the time to first stage web font repaint, making your most used web font reflow occur sooner rather than later.
Cons
- All the existing Cons of the FOFT approach.
- Introduces a small amount overhead in that the subset roman font loaded in the first stage is duplicated by the full roman web font loaded in the second stage. This is the price we’re paying to minimize reflow.
- License restriction: Requires subsetting.
Verdict: Use one of the improved Critical FOFT variations below.
Critical FOFT with Data URI
This variation of the Critical FOFT approach simply changes the mechanism through which we load the first stage. Instead of using our normal font loading JavaScript API to initiate a download, we simply embed the web font as a inline Data URI directly in the markup. As previously discussed, this will block initial render but since we’re only embedding a very small subset roman web font this is a small price to pay to mostly eliminate FOUT.
- Demo: Critical FOFT with Data URI (includes sessionStorage trick for repeat view optimization)
- Compare source code for Critical FOFT with Data URI against Critical FOFT
- Read more: Web Font Anti-Pattern: Data URIs—But Wait
Pros
- All the existing Pros of the Critical FOFT approach.
- Eliminates FOIT and greatly reduces FOUT for the roman font. A small reflow will occur for additional characters loaded in the second stage and when the other weights and styles are loaded, but it will have a much smaller impact.
Cons
- All the existing Cons of the Critical FOFT approach.
- The small inlined Data URI will marginally block initial render. We’re trading this for highly reduced FOUT.
- Self hosting: Required.
Verdict: This is the current gold standard, in my opinion.
Critical FOFT with preload
This variation of the Critical FOFT approach simply changes the mechanism through which we load the first stage. Instead of using our normal font loading JavaScript API to initiate a download, we use the new preload
web standard as described above in the preload
method. This should trigger the download sooner than previously possible.
- Demo: Critical FOFT with
preload
(includes sessionStorage trick for repeat view optimization) - Compare source code for Critical FOFT with preload against Critical FOFT
- Read more: The Web Fonts: Preloaded, Use With a Font Loading Strategy section
Pros
- All the existing Pros of the Critical FOFT approach.
- Rendering performance: Downloads should trigger higher up in the waterfall than with previous methods. I’d guess this is even more dramatic with HTTP headers but haven’t yet confirmed this hunch. This method is better than Critical FOFT with Data URI in that it can use the browser cache for repeat requests, rather than re-requesting the same web font data with every server markup request.
Cons
- All the existing Cons of the Critical FOFT approach.
- Use only with a single web font format.
- As stated above, browser support is limited—only Blink at time of writing.
preload
can marginally delay initial render (note data for this comparison was gathered on a site that was using Critical CSS)- Self hosting: Probably required.
66 Comments
@gnarc
Yes you did, and the context is lost. Just being able to rollover —or soft click on mobile — to view what the acronym stands for would be great. Some kind of sidenote with the direct link to the glossary the first time the term appears would be better. typographyatpsu.tumblr.com… Truncated
@zachleat
how many fonts you got
@polarbirke
Usually two typefaces for client projects, totaling 4-5 fonts (reg/bold of a display typeface, reg/bold/italic of the text work horse).
@stefanjudis
I'm still recommending it. :D 🙈
@m_yxnk
every time I'm on your blog for some other reason, I still click that one just to bump up the count
@mmatuzo
All time classic!
@keithfreund
I've read it a zillion times and when I hire new people, it's one of the 3 performance articles I ask everyone to read, along with these: addyosmani.com/blog/script-pr… csswizardry.com/2018/11/css-an…
@TheGreenGreek
Similarly, this is mine. Originally written a while ago but I try to keep it updated. I had to add the year to the title to beat my own search ranking with the Medium version even though I set a canonical link. sia.codes/posts/making-g…
@nhoizey
This is still my main destination when I want to find informations about font loading, reading at least parts of it 3 or 4 times a year, I guess. 👍
@x8BitRain
What is FOUT/FOFT/FOIT?
@nhoizey
Je ne dirais pas que ˋfont-loading` remplace complètement l’API. Elle reste utile pour gérer des applications synchronisées de multiples fontes, par exemple : zachleat.com/web/comprehens…
@ryuran78
Oui evidement, avant font-display le js était obligatoire pour réaliser l'équivalent. L'API est plus complete et permet plus que ça.
@keithfreund
Usually for an above-the-fold font weight or two via one of the strategies covered in this page: zachleat.com/web/comprehens…
@sky_chavda
Want to learn more about optimizing CSS for performance? Check out these resources 👇 web.dev zachleat.com/web/comprehens…
Shaun O'Connell Disqus
13 Jul 2016zachleat Disqus
14 Jul 2016Jonathan Bidston Disqus
26 Sep 2016Alex Bell Disqus
13 Jul 2016zachleat Disqus
14 Jul 2016zachleat Disqus
14 Jul 2016Alex Bell Disqus
15 Jul 2016zachleat Disqus
15 Jul 2016Ramanan V Disqus
24 Jul 2016Ramanan V Disqus
24 Jul 2016James Deering Disqus
14 Jul 2016zachleat Disqus
14 Jul 2016Shaun O'Connell Disqus
14 Jul 2016Mit10 Disqus
15 Jul 2016Tom Disqus
17 Jul 2016zachleat Disqus
14 Aug 2017Serge Zarouski Disqus
17 Jul 2016zachleat Disqus
05 Aug 2016Ying Zhang Disqus
23 Jul 2016zachleat Disqus
16 Dec 2016ZenMaster Disqus
27 Dec 2016zachleat Disqus
14 Aug 2017Tracker1 Disqus
27 Jul 2016zachleat Disqus
28 Jul 2016Peter Y. Chuang Disqus
31 Jul 2016zachleat Disqus
05 Aug 2016Peter Y. Chuang Disqus
15 Aug 2016Chris J. Zähller Disqus
22 Apr 2017chhola Disqus
25 Aug 2016Francis Boudreau Disqus
31 Jan 2017Tommy Mathiesen Disqus
08 Sep 2016zachleat Disqus
08 Sep 2016Tommy Mathiesen Disqus
08 Sep 2016zachleat Disqus
08 Sep 2016Chris J. Zähller Disqus
05 Oct 2016zachleat Disqus
16 Dec 2016Chris J. Zähller Disqus
16 Dec 2016Chris J. Zähller Disqus
22 Apr 2017zachleat Disqus
27 Apr 2017MD Yusuf Disqus
25 Oct 2016zachleat Disqus
14 Dec 2016Francesco Bedussi Disqus
22 Nov 2016zachleat Disqus
22 Nov 2016zachleat Disqus
14 Dec 2016Johny Varsami Disqus
08 Dec 2016zachleat Disqus
30 Jan 2017Francis Boudreau Disqus
30 Jan 2017zachleat Disqus
30 Jan 2017Francis Boudreau Disqus
30 Jan 2017Luke Cavanagh Disqus
21 Jul 2017nil2 Disqus
23 Aug 2017Duncan Wilcox Disqus
26 Aug 2017