← All guides

Menu and LCP: how navigation blocks your largest contentful paint

Menu font loading: preventing layout shift and invisible text

font-display strategies for navigation menus—balancing brand consistency with LCP and CLS.

You spent hours picking the perfect typeface for your store. The menu labels use a custom sans-serif that matches your brand exactly. But every time you test your store on mobile, you see a flash of invisible text where the menu should be, followed by a layout shift when the font finally loads. Your CLS score is 0.18, which Google marks as “needs improvement,” and you suspect the menu font is the culprit.

The problem is not the font itself. The problem is how the browser handles font loading. By default, most browsers hide text until custom fonts finish downloading, which can take 300 to 800 milliseconds on a slow mobile connection. The menu area sits empty, the page looks broken, and when the font finally appears, it shifts everything below it. This hurts both LCP (because significant content is delayed) and CLS (because the layout moves).

This article explains the five font-display strategies, which one works best for navigation menus, and how to implement font loading that does not break your brand or your Core Web Vitals.

Quick read
  • Browsers hide text during font download by default, causing Flash of Invisible Text (FOIT).
  • font-display controls whether text shows immediately or waits for the custom font.
  • font-display: swap is the best choice for menu text—shows fallback immediately, swaps when ready.
  • System fonts (already on the device) have zero loading cost and eliminate FOIT/FOUT entirely.
  • Font preloading can reduce delay but does not eliminate the risk of layout shift.

The Two Font Loading Problems: FOIT and FOUT

When a browser encounters a custom web font, it has to decide what to do with text that uses that font while the font file is still downloading. There are two possible behaviors, each with tradeoffs.

Flash of Invisible Text (FOIT)

The browser hides the text until the font loads. The text space is reserved, but nothing appears. If the font takes 500 milliseconds to download, the user stares at blank space for half a second. This is the default behavior in most browsers.

FOIT is bad for LCP because significant text content is not visible until the font loads. It is also bad for usability—users cannot read the menu, which may make them think the page is broken.

Flash of Unstyled Text (FOUT)

The browser shows the text immediately using a fallback font (usually a system font like Arial or Helvetica), then swaps to the custom font once it loads. The text is readable right away, but the swap can cause a layout shift if the fallback font and the custom font have different dimensions.

FOUT is better for LCP (text appears immediately) but can hurt CLS if not handled carefully. The key is matching the fallback font’s dimensions to the custom font as closely as possible.

The Five font-display Strategies

The font-display CSS property controls how browsers handle the font loading period. There are five possible values, each with different timing and fallback behavior.

Value Behavior Best for
auto Browser decides (usually FOIT for 3 seconds, then FOUT) Not recommended—unpredictable
block FOIT for up to 3 seconds, then swap Decorative text where brand font is critical
swap FOUT immediately—show fallback, swap when ready Body text, navigation menus
fallback FOIT for 100ms, then FOUT for 3s, then stick with fallback Headings where font matters but speed matters more
optional FOIT for 100ms, then use fallback if font not ready, never swap Performance-critical text

For navigation menus, font-display: swap is almost always the right choice. It makes the menu readable immediately (good for LCP and usability) and swaps to the brand font as soon as it loads (good for brand consistency). The risk is layout shift, which we will address in a moment.

How to Use font-display in Your Stylesheet

If your font is defined in your CSS using @font-face, add the font-display property:

@font-face {
  font-family: 'BrandFont';
  src: url('/fonts/brandfont.woff2') format('woff2');
  font-display: swap;
}

.menu-link {
  font-family: 'BrandFont', -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
}

The font-family stack is important. List your custom font first, then system fonts as fallbacks. This ensures the menu text appears instantly in a system font, then swaps to your custom font when it loads.

If your font is loaded from Google Fonts, add the display=swap parameter to the URL:

<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap">

Google Fonts will automatically inject font-display: swap into the generated CSS.

Preventing Layout Shift with Fallback Font Matching

The problem with font-display: swap is that if the fallback font and the custom font have different sizes or spacing, the text will reflow when the swap happens, causing a layout shift. This is especially noticeable in navigation menus, where even a small shift can move the entire page content.

The solution is to adjust the fallback font to match the custom font’s dimensions using the size-adjust, ascent-override, and descent-override properties. These are relatively new CSS features (supported in all modern browsers as of 2024) that let you tweak a fallback font to match a custom font.

Here is an example:

@font-face {
  font-family: 'BrandFont';
  src: url('/fonts/brandfont.woff2') format('woff2');
  font-display: swap;
}

@font-face {
  font-family: 'BrandFont Fallback';
  src: local('Arial');
  size-adjust: 107%;
  ascent-override: 95%;
  descent-override: 25%;
}

.menu-link {
  font-family: 'BrandFont', 'BrandFont Fallback', Arial, sans-serif;
}

The size-adjust property scales the fallback font so its x-height matches the custom font. The ascent-override and descent-override properties adjust the vertical spacing.

Finding the right values requires testing. Tools like Fallback Font Generator by Simon Hearne automate this process. You input your custom font, and the tool calculates the optimal override values for common system fonts.

Matching fallback fonts this way reduces CLS from font swapping to near zero. The text still swaps from the fallback to the custom font, but the layout does not shift because the dimensions are identical.

System Fonts: The Zero-Cost Alternative

The fastest font is no font at all—or rather, a system font that is already installed on the user’s device. System fonts have zero download cost, zero parse cost, and zero layout shift risk.

Here is a modern system font stack that looks good on all platforms:

.menu-link {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', Arial, sans-serif;
}

This gives you:

  • San Francisco on macOS and iOS
  • Segoe UI on Windows
  • Roboto on Android
  • Platform-appropriate defaults on Linux

System fonts are not as distinctive as custom typefaces, but they are clean, readable, and optimized for their platforms. Many high-traffic sites (GitHub, Medium, WordPress.com) use system fonts for performance reasons.

If your brand guidelines require a custom font, consider using system fonts for the navigation menu and reserving the custom font for headings, hero text, or body copy where it has more visual impact. The menu is functional UI. It does not need to carry the brand as heavily as a headline does.

Preloading Fonts to Reduce FOUT Duration

Even with font-display: swap, there is a delay between the fallback appearing and the custom font swapping in. On a slow connection, this can be 500 milliseconds or more. Preloading the font file tells the browser to fetch it with high priority early in the page load, which reduces the swap delay.

<link rel="preload" href="/fonts/brandfont.woff2" as="font" type="font/woff2" crossorigin>

The crossorigin attribute is required even if the font is hosted on your own domain, because fonts are fetched with CORS mode.

Preloading does not eliminate FOUT, but it reduces the duration. If your menu font is small (under 50KB) and critical to the brand, preloading can improve the perceived performance.

Caution: Preloading competes with other critical resources like CSS and JavaScript. Only preload fonts that are used above the fold and are truly essential. Preloading too many resources can slow down the initial render.

Icon Fonts vs Inline SVG

Many navigation menus use icon fonts (Font Awesome, Remix Icon, Material Icons) for arrows, search icons, and hamburger icons. Icon fonts are just fonts, so they have the same loading characteristics as text fonts: FOIT by default, layout shift risk, and download cost (often 50-150KB for a full icon font).

Inline SVG is a better choice for menu icons. An inline SVG is part of the HTML, so it appears immediately with no network request and no font loading delay. It also scales perfectly and supports CSS styling.

Here is an example:

<button class="menu-toggle" aria-label="Open menu">
  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <line x1="3" y1="12" x2="21" y2="12"></line>
    <line x1="3" y1="6" x2="21" y2="6"></line>
    <line x1="3" y1="18" x2="21" y2="18"></line>
  </svg>
</button>

This hamburger icon is 200 bytes. A full icon font with 2,000 icons you will never use is 150KB. Inline SVG also eliminates the FOIT problem—the icon appears instantly because it is part of the HTML.

Modern menu apps like Navi+ use inline SVG for all icons, which is one reason they avoid the layout shift issues that plague icon font-based menus.

Shopify-Specific Font Loading

Shopify themes typically load fonts in one of three ways: from Google Fonts, from Shopify’s CDN (via the theme settings font picker), or from custom font files in the theme’s assets folder.

Google Fonts

If your theme uses Google Fonts, make sure the display=swap parameter is in the URL. Check theme.liquid for a line like this:

<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600">

Change it to:

<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap">

Shopify CDN Fonts

If your theme uses Shopify’s built-in font picker (Settings > Typography), Shopify serves the fonts from its CDN and automatically includes font-display: swap as of 2023. You do not need to do anything.

Custom Font Files

If you uploaded custom font files to your theme’s assets folder, add font-display: swap to the @font-face declaration in your CSS.

Measuring Font Loading Impact

Use Chrome DevTools to see how fonts affect your LCP and CLS.

Check for FOIT

Open DevTools, go to the Network tab, filter by “Font,” and reload the page. Watch the menu area. If the menu text is invisible for a noticeable period before appearing, you have FOIT. Add font-display: swap to fix it.

Check for Layout Shift

Open DevTools, go to the Performance tab, click Record, reload the page, and stop the recording. Look at the Experience section for red “Layout Shift” bars. Click one to see which elements shifted. If the menu text is listed, the font swap caused a layout shift. Use fallback font matching to fix it.

Measure CLS

Run a Lighthouse test (in DevTools or PageSpeed Insights). Look at the CLS score and the “Avoid large layout shifts” diagnostic. If fonts are listed as a cause, you need better fallback matching or should consider switching to system fonts.

Quick winIf your menu uses a custom font and you are seeing CLS issues, try switching to a system font stack as a test. Run Lighthouse again. If CLS drops significantly, the custom font was the problem. You can then decide whether the brand value of the custom font is worth the performance cost, or implement fallback matching to get both.

What Good Menu Font Loading Looks Like

A well-optimized navigation menu handles fonts in one of two ways.

Option 1: System fonts. No custom fonts, zero download cost, zero FOIT, zero layout shift. The menu appears instantly with native-looking text.

.menu-link {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
}

Option 2: Custom font with swap and fallback matching. The menu text appears immediately in a system font, then swaps to the custom font without layout shift.

@font-face {
  font-family: 'BrandFont';
  src: url('/fonts/brandfont.woff2') format('woff2');
  font-display: swap;
}

@font-face {
  font-family: 'BrandFont Fallback';
  src: local('Arial');
  size-adjust: 107%;
}

.menu-link {
  font-family: 'BrandFont', 'BrandFont Fallback', Arial, sans-serif;
}

Both approaches result in a menu that is readable within the first second of page load, which is what LCP measures. The first approach is simpler and faster. The second approach preserves brand consistency with minimal performance cost.

For icons, use inline SVG instead of icon fonts. The icons appear instantly, scale perfectly, and cost a few hundred bytes instead of 150KB.

Most stores that switch from default font loading (FOIT, no fallback matching) to one of these patterns see CLS drop by 0.05 to 0.15 points and LCP improve by 200 to 400 milliseconds on mobile. The menu looks the same to the user, but the browser renders it faster and more smoothly.

This article is part of the larger guide on Menu and LCP: how navigation blocks your largest contentful paint.

Share Facebook X

Get started with Navi+ AI Menu Builder

Pick your platform — free to install, live in minutes.