w3tweaks.com · CSS Tutorial

CSS @font-face

font-display decoded, variable fonts live, and the adjusted fallback that kills layout shift.

Tab 1

font-display — All 5 Values Explained

Most tutorials just say "use font-display: swap". But each value makes a different trade-off between performance and visual stability. Click a value to see its timeline and when to actually use it.

swap
Invisible → fallback → custom

Tiny block period. Shows fallback immediately, swaps to custom when ready. Can cause layout shift. Best for body text.

block
Invisible text up to 3s

Short block period. Hides text while font loads. Worst for performance. Only justified for icon fonts.

fallback
Short swap window

Tiny block + ~3s swap window. If font doesn't load in time, uses fallback permanently. Good middle ground.

optional
Load once, cache forever

Tiny block, NO swap. Browser may not use the font on slow connections. Best for Core Web Vitals — zero CLS.

auto
Browser decides

Browser picks the strategy. Usually behaves like block. Avoid — you lose control over the font loading experience.

Font Loading Timeline — What the User Sees
swap
·
fallback text
custom font loaded ✓
✓ Readable fast
block
invisible text ✗
custom font loaded ✓
✗ FOIT — bad
fallback
·
fallback
swap window
fallback if missed
Good middle ground
optional
·
fallback (or custom if cached) — zero shift
Zero CLS ✓
auto
usually invisible (like block)
custom font loaded ✓
Browser decides
Decision guide:
swap — body text, headings. Users need to read immediately. Accept the swap shift.
block — icon fonts only. Never for readable text.
fallback — when you want swap behavior but with a deadline. Font must load in ~3s or fallback wins.
optional — best for Core Web Vitals. Zero layout shift. Font loads once and is cached for all future visits.
auto — avoid. You lose control.
Tab 2

Variable Fonts in @font-face

A variable font is a single file containing an entire type family. One @font-face rule replaces 8+ separate font files. Drag the sliders to see the weight axis in action.

Live Variable Font Demo
The quick brown fox
400
100
14
42px
0px

The preview uses your system sans-serif font, so weight always works but wdth and opsz only render visually if your OS has a variable system font (recent macOS/iOS/Windows). The CSS shown is what you'd write for a real variable font like RobotoFlex.

@font-face {
  font-family: "RobotoFlex";
  src: url("roboto-flex.woff2") format("woff2");
  font-weight: 100 900; /* range — not a single value */
  font-display: swap;
}

/* Current settings */
.heading {
  font-family: "RobotoFlex";
  font-weight: 400;
  font-variation-settings:
    "wght" 400,
    "wdth" 100,
    "opsz" 14;
  font-size: 42px;
}
❌ Common bug — all weights look the same
If your variable font renders every weight identically, you forgot the weight range in @font-face:
@font-face {
  font-weight: 400; ← single value!
  /* browser ignores 100-900 */
}
✅ Fixed — declare the range
Set font-weight to a range matching the font's actual axis range:
@font-face {
  font-weight: 100 900; ← range ✓
  /* all weights now work */
}
Tab 3

The Adjusted Fallback — Zero Layout Shift

font-display: swap causes layout shift because the fallback font has different metrics than your custom font. The adjusted fallback pattern uses size-adjust, ascent-override and descent-override to make the fallback match your font's dimensions exactly.

❌ Without adjusted fallback
1

Page loads — shows Arial

Arial is 107% the width of Inter. Layout renders with Arial metrics.

2

Font loads → JUMP

Inter swaps in. Metrics are different. Everything reflows. CLS score spikes.

Headline in Arial
Body text shifts when Inter loads in — lines reflow, buttons move.
CLS score: 0.15 ← bad
✅ With adjusted fallback
1

Page loads — shows adjusted Arial

Arial adjusted to Inter's metrics: same width, same ascenders, same descenders.

2

Font loads → no jump

Inter swaps in. Metrics match. Layout doesn't reflow. CLS stays near 0.

Headline in Inter (matched)
Body text stays perfectly still when Inter loads — metrics were pre-matched.
CLS score: 0.001 ← excellent
The Adjusted Fallback Pattern — Copy This
/* Step 1: Load your custom font */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-variable.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
}

/* Step 2: Create an adjusted system font fallback */
@font-face {
  font-family: 'Inter-Fallback';
  src: local('Arial'); /* use system Arial as base */
  size-adjust: 107%; /* scale to match Inter's width */
  ascent-override: 90%; /* match Inter's cap height */
  descent-override: 22%; /* match Inter's descenders */
  line-gap-override: 0%;
}

/* Step 3: Stack — Inter first, adjusted fallback second */
body {
  font-family: 'Inter', 'Inter-Fallback', Arial, sans-serif;
}
Inter → Arial adjusted — these are the values you'd plug into the @font-face above:
font-family: 'Arial';
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;

Exact values for size-adjust, ascent-override, and descent-override depend on the specific font pair. Use the Perfect Font Fallback tool or Fontaine (an npm package) to auto-generate the correct values for your font.

Browser support for size-adjust, ascent-override, descent-override: Chrome 92+, Firefox 89+, Safari 17+. All safe for production in 2026. On unsupported browsers, the fallback font is used without metric adjustment — no errors, just a slightly different layout on old browsers.
Read the tutorial