# Font Display Strategy

**MUST** · **ID:** `performance-font-display-strategy` · **Category:** performance
**Source:** [Web Platform](https://web.dev/)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/performance-font-display-strategy

> MUST: Use `font-display: swap` to prevent FOIT (invisible text). Preconnect to font CDNs. Preload critical fonts. Match fallback font metrics with `size-adjust` to minimize CLS during font swap.

Prevent invisible text (FOIT) and layout shift from web fonts with proper loading strategies

Web fonts can cause Flash of Invisible Text (FOIT) affecting LCP, and layout shift when fonts swap. font-display: swap shows fallback text immediately. Metric matching with size-adjust minimizes CLS.

## Bad — do not do this

`performance-font-display-strategy-bad`

```tsx
export function FontDisplayStrategyBad() {
  return (
    <div className="w-full max-w-md">
      <div className="bg-card border border-border rounded-lg p-6">
        <div className="space-y-4">
          {/* Simulating font loading without font-display: swap */}
          <div className="relative">
            <h2 className="text-xl font-bold text-transparent animate-pulse bg-muted">
              FOIT: Flash of Invisible Text
            </h2>
            <p className="text-xs text-muted-foreground mt-1">
              Text is invisible while font downloads
            </p>
          </div>

          <div className="border-t border-border pt-4">
            <p className="text-sm text-muted-foreground leading-relaxed">
              Without <code className="bg-muted px-1 rounded">font-display</code>,
              browsers hide text for up to 3 seconds while waiting for web fonts.
              Users see a blank page instead of content.
            </p>
          </div>

          <div className="bg-muted/50 p-3 rounded text-xs font-mono">
            <span className="text-muted-foreground">/* Missing font-display */</span>
            <br />
            @font-face {'{'}
            <br />
            &nbsp;&nbsp;font-family: 'CustomFont';
            <br />
            &nbsp;&nbsp;src: url('/font.woff2');
            <br />
            &nbsp;&nbsp;<span className="text-error">/* No font-display! */</span>
            <br />
            {'}'}
          </div>
        </div>
      </div>
      <div className="mt-4 p-3 bg-error/10 border border-error/30 rounded text-sm">
        <p className="font-medium text-error">Problems:</p>
        <ul className="text-xs text-error/80 mt-1 space-y-1 list-disc pl-4">
          <li>Text invisible during font download (FOIT)</li>
          <li>No preconnect to font origins</li>
          <li>No fallback font metrics matching</li>
          <li>Hurts LCP when text is the largest element</li>
        </ul>
      </div>
    </div>
  );
}
```

## Good — do this

`performance-font-display-strategy-good`

```tsx
export function FontDisplayStrategyGood() {
  return (
    <div className="w-full max-w-md">
      <div className="bg-card border border-border rounded-lg p-6">
        <div className="space-y-4">
          {/* Text visible immediately with system font */}
          <div className="relative">
            <h2 className="text-xl font-bold">
              Instant Text Visibility
            </h2>
            <p className="text-xs text-muted-foreground mt-1">
              Fallback font shown immediately, swaps when loaded
            </p>
          </div>

          <div className="border-t border-border pt-4">
            <p className="text-sm text-muted-foreground leading-relaxed">
              With <code className="bg-muted px-1 rounded">font-display: swap</code>,
              text renders immediately with a fallback font, then swaps when the
              web font loads. Users see content right away.
            </p>
          </div>

          <div className="bg-muted/50 p-3 rounded text-xs font-mono">
            <span className="text-success">/* Optimized font loading */</span>
            <br />
            @font-face {'{'}
            <br />
            &nbsp;&nbsp;font-family: 'CustomFont';
            <br />
            &nbsp;&nbsp;src: url('/font.woff2');
            <br />
            &nbsp;&nbsp;<span className="text-success">font-display: swap;</span>
            <br />
            {'}'}
          </div>
        </div>
      </div>
      <div className="mt-4 p-3 bg-success/10 border border-success/30 rounded text-sm">
        <p className="font-medium text-success">Best Practices:</p>
        <ul className="text-xs text-success/80 mt-1 space-y-1 list-disc pl-4">
          <li><code>font-display: swap</code> shows text immediately</li>
          <li>Preconnect to font CDNs for faster loading</li>
          <li>Size-adjust on fallback minimizes CLS</li>
          <li>Preload critical fonts in document head</li>
        </ul>
      </div>
    </div>
  );
}
```

## References

- [Optimize web fonts](https://web.dev/articles/optimize-webfont-loading)
- [font-display MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@font-face/font-display)
