# Font-display swap configured on all web fonts

- **Pattern:** `ab-002009` (`performance-core.image-media-optimization.font-display`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002009
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002009)

## Why it matters

Without `font-display`, the browser's default behavior is to block text rendering for up to 3 seconds while waiting for the web font to download — Flash of Invisible Text (FOIT). On slow connections, users see blank text boxes where your content should be (ISO-25010 time-behaviour). Even on fast connections, FOIT contributes to CLS when the font finally loads and changes character widths, causing visible layout shift. Every web font without `font-display: swap` is a potential 3-second invisible-text window.

## Severity rationale

Medium because missing `font-display` causes up to 3 seconds of invisible text on slow connections and triggers CLS-contributing font swaps when the web font finally loads.

## Remediation

Add `font-display: swap` to every `@font-face` declaration so the browser shows fallback text immediately and swaps in the web font when ready:

```css
/* src/styles/fonts.css */
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom-regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
```

With `next/font/google`, `font-display: swap` is applied automatically:
```tsx
// src/app/layout.tsx
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })  // font-display: swap included
```

With Google Fonts via `<link>`, append `&display=swap` to the URL:
```html
<link href="https://fonts.googleapis.com/css2?family=Inter&display=swap" rel="stylesheet">
```

Set a size-similar fallback font stack (e.g., `Arial` for `Inter`) to minimize the visual jump on swap.

## Detection

- **ID:** `font-display`
- **Severity:** `medium`
- **What to look for:** Count all relevant instances and enumerate each. Check `@font-face` declarations for `font-display` property. Look in CSS files, font loading libraries (next/font, Google Fonts, Typekit), or `<link>` tags for web fonts. Verify all font faces have `font-display` set to `swap`, `optional`, or other non-blocking value.
- **Pass criteria:** All `@font-face` declarations include `font-display: swap` (or another non-blocking value). At least 1 implementation must be verified. Fonts are loaded asynchronously, and fallback fonts display immediately.
- **Fail criteria:** `font-display` is missing (defaults to `auto`, blocking on some browsers), or set to `block` (FOIT). Fonts block text rendering for 3+ seconds.
- **Skip (N/A) when:** The project uses only system fonts or no web fonts.
- **Detail on fail:** Specify the font loading strategy. Example: `"Five @font-face rules with no font-display; text blocks for 2.5s while font loads (FOIT). No fallback font specified"` or `"font-display: block on all fonts; poor UX on slow networks"`.
- **Remediation:** `font-display: swap` shows fallback text immediately and swaps in the web font when ready:

  ```css
  @font-face {
    font-family: 'CustomFont';
    src: url('font.woff2') format('woff2');
    font-display: swap;  /* show fallback immediately, swap in web font */
  }
  ```

  With next/font:
  ```tsx
  import { Inter } from 'next/font/google'
  const inter = Inter()  // already optimized with font-display: swap
  ```

  With Google Fonts link:
  ```html
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Inter&display=swap" rel="stylesheet">
  ```

## External references

- iso-25010 performance-efficiency.time-behaviour

Taxons: performance

HTML version: https://auditbuffet.com/patterns/ab-002009
