CSS Fonts Table
| Piece | What it does | Field note |
|---|---|---|
font-family stack | Fallback chain | Always end sans-serif/monospace - the generic IS the fallback |
font-display: swap | Kill the invisible text | Fallback shows instantly, webfont swaps in - FOIT solved |
size-adjust | Match fallback metrics | Fallback scaled to webfont width - swap stops shifting layout |
Variable fonts | One file, all weights | font-weight: 1-1000 continuous - no 400/700 file pairs |
font-optical-sizing | Display vs text cut | Auto per-size axis - display cuts tighter at large sizes |
line-height 1.5 | The frame (again) | Unitless inherits as ratio - every font change stays proportional |
font shorthand resets | The silent wipe | font: 16px sans resets weight/style/line-height - longhands in components |
system-ui stack | The native look | Native font = zero download + perfect rendering - the default-first answer |
Web font loading has two failure modes and one fix each: invisible text (FOIT - the fallback hidden while the webfont loads) dies with font-display: swap - the fallback shows instantly and the webfont swaps in - and the swap-induced LAYOUT SHIFT dies with size-adjust, which scales the fallback to the webfont's metrics.
Bottom line: the stack always ends with a generic (sans-serif is the fallback, not a style choice), consider the system-ui stack FIRST (zero download, perfect native rendering - the default-first answer), and remember the font shorthand resets weight, style and line-height: the backgrounds shorthand trap in a typographer's clothing.
The honest part: variable fonts end the 400/700 file-pair era - one file carries the continuous weight axis (font-weight: 350 works), often smaller than the two static files it replaces. Optical sizing (auto) then cuts display sizes tighter automatically, like metal type always did.
How to use
- Ship the swap pair: font-display: swap plus size-adjust on the @font-face fallback (ascent-override, size-adjust tuned to match metrics) - instant text, minimal shift.
- Try the zero-download stack first: font-family: system-ui, sans-serif - the native font renders perfectly and costs nothing; add a webfont only when brand demands it.
- One variable file: @font-face { src: url(Inter.woff2) } with font-weight: 100 900 declared - every weight from one cached file instead of pairs per weight.
Frequently asked questions
What is the difference between FOIT and FOUT, and which should I choose?
Two ways font loading can look. FOIT (flash of invisible text): the browser hides the text while the webfont loads - no layout shift, but blank content for up to the block period (bad on slow networks; unreadable content is worse than different-looking content). FOUT (flash of unstyled text): the fallback shows immediately, then swaps when the webfont arrives - content always visible, but metrics differ so the layout shifts at swap. font-display picks: block (FOIT-ish), swap (FOUT, the recommended default), optional (webfont only if cached - near-zero shift, sometimes fallback forever). Choose swap, then fix the shift with size-adjust rather than hiding text.
How does size-address reduce the layout shift that swap causes?
Metric matching. The shift at swap happens because fallback and webfont occupy different widths - every line re-wraps when the real font lands. size-adjust (on a fallback @font-face declaration) scales the fallback glyph metrics - size, ascent, descent - to approximate the webfont's dimensions, so the fallback layout is nearly identical and the swap becomes a texture change instead of a reflow. Tools compute the percentages automatically from font metadata. It is fallback tuning: the fallback never looks right, it just OCCUPIES the right space - which is exactly what swap needs from it.
Why are variable fonts often smaller than two static weights?
Shared outlines. Static fonts duplicate every glyph per weight - Regular and Bold each carry full glyph sets. A variable font stores one master outline per glyph plus a variation axis: the Bold deltas are small adjustments to the Regular outlines. The result: one file covering weight 100-900 frequently ships smaller than the two static files it replaces, with every intermediate weight available (350 for large text, 450 for small - weights static files never offered). The caveat: rendering cost is slightly higher, and design axes beyond weight (width, optical size) grow the file - pick the axes you use and subset the rest.
When is the system-ui stack the right final answer?
More often than brands expect. system-ui renders the OS native font: zero download (no FOIT, no FOUT, no shift - the metrics ARE the platform's), perfect hinting and rendering, and an interface that feels native on every device. Content sites, documentation, dashboards and tools gain speed and lose nothing but brand distinctiveness. The honest case for a webfont: brand identity where typography IS the brand, or display headlines with character. The hybrid is common and correct: system-ui for UI chrome and body text, one webfont for display headlines - the webfont downloads are then small, and their swap shift affects one element, not the page.