Fonts without flashes

Play this lesson

Invisible words

Kai drew a custom font with shard-shaped letters for Shard Garden's menus. On slow connections, players see buttons with no words for up to three seconds. On other phones, the words appear in a plain font, then jump when the custom one arrives.

Both come from how browsers wait for web fonts, font files downloaded with the page. We can choose how they wait.

The font display timeline

From the moment text needs a web font that hasn't arrived, three periods follow:

web.dev says Chromium and Firefox block for up to 3 seconds by default, and Safari for as long as it takes.

Quick check

Players tell Priya they see buttons with no words for up to three seconds. Which period is that?

  1. The block period
  2. The swap period
  3. The failure period
Show the answer

The block period

In the block period, text is drawn in an invisible stand-in font. Chromium and Firefox wait up to 3 seconds there.

font-display

We set it inside @font-face. auto lets the browser decide.

Pick the value

Shard Garden's in-game score must never be invisible and must never jump mid-match. Which font-display fits?

  1. block
  2. swap
  3. optional
Show the answer

optional

optional waits 100 ms at most and never swaps later, so the score never vanishes or jumps. If the font is late, this visit uses the fallback. block hides the text, and swap can jump.

Smaller font files

WOFF2 font files are compressed with Brotli, and web.dev says they compress 30% better than the older WOFF. Its advice: use only WOFF2.

A Latin font usually has 100 to 1,000 glyphs (letter shapes). A Chinese, Japanese or Korean font can have over 10,000. So we subset: split the font into files by alphabet, and give each @font-face a unicode-range. The browser downloads a file only if the page uses a character in its range.

Quick check

With unicode-range set, a player reading Shard Garden in English still downloads the Japanese font file.

Show the answer

False

If the page uses no character in a font file's unicode-range, that file is not downloaded. The Japanese file only loads when Japanese text is on screen.

Found early

The browser only learns about a font when it reads the CSS that uses it. Two ways to start sooner:

Fonts are always fetched in a special CORS mode, so crossorigin is needed even on our own domain. Preload ignores unicode-range, so preload only a file every page needs.

Bug hunt

index.html
<link rel="preload"  href="/f/shard.woff2"  as="font"  type="font/woff2">

DevTools shows the font fetched twice: the preload isn't used. What is missing?

  1. The crossorigin attribute
  2. A font-display value
  3. A unicode-range
Show the answer

The crossorigin attribute

Fonts are fetched in CORS mode. Without crossorigin, the preload's mode doesn't match the real font request, so the browser can't reuse it and downloads the file again.

Match the stand-in

When a swap happens, text jumps if the fallback is wider or narrower than the web font: that is a layout shift. The size-adjust descriptor scales a local fallback font, for example size-adjust: 90%, so it takes almost the same space as our custom font.

Bolt says: The page is fast now. Next: the game loop, and how each frame spends its milliseconds.

Fact-checked 11 October 2026