Skip to content

[css-fonts] Add a font-display keyword to eliminate @font-face FOIT & layout shifts #7271

@xiaochengh

Description

@xiaochengh

Currently, we don't have any easy & sound way to eliminate FOIT & layout shifts caused by web fonts:

  • font-display: optional elimintes layout shifts and FOIT, but at the cost that the web font may not be used, which limits its usage
  • Other font-display values do not reduce layout shifts at all
  • size-adjust descriptor only reduces the layout shift but can't guarantee 0 CLS, and there might still be a FOIT. It's also complicated to use since we need to find out the correct value.

I'm proposing adding a new keyword to the font-display descriptor, tentative named font-display: critical, which:

  • Turns the @font-face a critical subresource of the style sheet, and hence makes it block the load event of the style sheet
  • Makes UA start loading such a @font-face eagerly; other font faces are still loaded lazily when used
  • It has the same Font Display Timeline as font-display: block

In this way, as long as the @font-face is defined in a render-blocking style sheet (which everyone knows about but hadn't been specified until recently), then it will block the first render of the document, and hence eliminate FOIT / layout shifts.

This feature is supposed to be used on web fonts that are truly critical, so that developers want to eliminate FOIT / CLS at a great cost of delaying rendering. It will be footgun-ish and shouldn't be used arbitrarily.

Use cases

Basic usage:

<head>
  <style>
  @font-face {
    font-family: my-font;
    font-display: critical;
    src: url(/my-font.ttf);
  }
  body { font-family: my-font; }
  </style>
</head>
<body>
  Text in my-font with absolutely no FOIT / CLS
</body>

There's also a particular use case I'd like to support: making a 3rd party web font render-blocking without knowing the font url.

Developer page:

<head>
  <link rel=stylesheet href="https://3p-fonts.com/cool-font.css?critical=yes">
  <style>body { font-family: cool-font; }</style>
</head>
<body>
  Text in cool-font with absolutely no FOIT / CLS
</body>

https://3p-fonts.com/cool-font.css?critical=yes:

@font-face {
  font-family: cool-font;
  font-display: critical;
  /* url is maintained by 3p-fonts.com. May change at any time; may even be generated. */
  /* Developers shouldn't preload this url or inline this style sheet. */
  src: url(/some-random-hash-123654ABCFED-or-whatever.ttf);
}

Possible discussions

  • Q: How about preloading the font?
  • A: Preloading just makes it less likely to cause FOIT / layout shifts, but there's no guarantee. And it doesn't work for the 3rd party web font use case.
  • Q: How about adding blocking=render to the font preload <link> to block rendering until the preload finishes?
  • A: Besides not working for 3rd-party web fonts, there are many issues with making preload explicitly interact with rendering, so we decided to remove blocking=render from preload. See more discussions at
  • Q: This further blocks rendering on a subsequent load, which is bad for loading performance
  • A: We can't fully eliminate subsequent loading behavior, since we must use external style sheet for 3p web fonts. However, there are ways to alleviate this by starting the font loading as early as possible (see below):
    • UA can use a preload scanner that not only preloads the external style sheet
    • UA can also use a preload scanner to scan the external style sheet response to preload the font
    • The external style sheet response can also have a Link header that preloads the font
  • Q: Why making it a font-display keyword?
  • A: This provides an even stronger alternative than font-display: block. So it doesn't make much sense to be a standalone descriptor and then interact with the other font-display values. That's also the reason why its font display timeline is the same as font-display: block, though it's mainly for spec completeness -- if we ever need to use such a font while it's still pending, it's likely a misuse.
  • Q: What if it blocks rendering indefinitely?
  • A: Render-blocking stylesheets and scripts can already block rendering indefinitely (or until the UA gives up), so it's not more footgun-ish than existing render-blocking things. And it allows developers to achieve a tradeoff that's hard to achieve before.

Possible blockers

  • "CSS critical subresource" seems very under-specified at the moment (Define which subresources block the DOM load event #1088). But I guess we can just say "critical subresources should at least include font-display: critical font faces" without having to fully define what other critical subresources are.

@tabatkins @chrishtr

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions