Fonts without flashes
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:
- Block period: text is drawn in an invisible stand-in font.
- Swap period: text is drawn in a visible fallback font, and swaps if the web font arrives.
- Failure period: the browser gives up and keeps the fallback.
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?
- The block period
- The swap period
- 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
- blockA short block period, and the font swaps in whenever it arrives.
- swapAn extremely small block period, and it swaps whenever it arrives.
- fallbackAn extremely small block period and a short swap period. Too late, and it stays on the fallback.
- optionalAn extremely small block period and no swap. web.dev puts the delay at 100 ms or less.
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?
- block
- swap
- 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.
- True
- False
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:
- Put the
@font-facerules in a<style>in the page's head, not in an external stylesheet. - Preload the most important file:
<link rel="preload" href="/f/shardline.woff2" as="font" type="font/woff2" crossorigin>.
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
1<link rel="preload"2 href="/f/shard.woff2"3 as="font"4 type="font/woff2">
DevTools shows the font fetched twice: the preload isn't used. What is missing?
- The crossorigin attribute
- A font-display value
- 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
- MDN: font-display developer.mozilla.org
- web.dev: Best practices for fonts web.dev
- MDN: unicode-range developer.mozilla.org
- MDN: rel=preload developer.mozilla.org
- MDN: size-adjust developer.mozilla.org