Understanding local() in @font-face: A Deep Dive

Learn how to build a responsive profile page with modern typography, media queries, and CSS Grid. Part three of our hands-on web development tutorial series.

Published on: 2025-01-22

Written by Schalk Neethling

Recently, I conducted some experiments with the local() function in CSS @font-face rules that revealed some interesting insights about how browsers handle different font weights, fallbacks, and the interaction of browser extensions. This investigation started with a simple question: How specific do we need to be when using local() to reference fonts which are potentially installed on a user’s system? This question came up when I was writing part three of the “Building a Profile Page with HTML and CSS” series.

The Initial Assumption

Usually should you load a custom web font such as Inter, as an example, you would have multiple @font-face declarations for each font weight and style you want to use. I was therefore curious how this would work when using local() in the @font-face declaration. I was honestly not sure at all but, if you open Font Book on macOS for example, one will see that a family is only listed once and all the different weights and styles are listed as variants.

So, even though you install multiple files (in cases where it is not a variable font) with names such as Inter-Regular, Inter-Bold etc. the family name is the same. This led me to wonder if a single @font-face declaration with local("Inter") would be sufficient to access all installed variants of the font family.

The Experiment

To test this assumption, I set up a simple experiment using HTML and CSS:

<h1>Hello! Which Font Family Am I?</h1>
<p>Lorem ipsum dolor sit amet consectetur...</p>
@font-face {
  font-family: Inter;
  src: local("Inter");
}

h1,
p {
  font-family: Inter;
}

The Results

I started with the Inter font family not installed locally. What I discovered was interesting. First the good news.

  1. A single @font-face declaration using local("Inter") was sufficient to access all installed variants of the font family.
  2. The browser automatically selected the appropriate font weight or style based on the element’s computed styles.
  3. Using Chrome’s developer tools, I could see:
    • For the h1: PostScript name: Inter-Bold
    • For the p: PostScript name: Inter-Regular
  4. Using font-weight or font-style on an element will have the expected result of the browser using the appropriate variant of the font.

Note: I wanted to include the following note from MDN Web Docs as I only tested this on macOS and there might be some differences between operating systems and fonts. “For OpenType and TrueType fonts, <font-face-name> is used to match either the Postscript name or the full font name in the name table of locally available fonts. Which type of name is used varies by platform and font, so you should include both of these names to assure proper matching across platforms. Platform substitutions for a given font name must not be used.”

What the above note essentially states is that you should do the following to be safe and ensure that you are matching the correct font (this does not change the remainder of the findings in this article):

@font-face {
  font-display: swap;
  font-family: Inter;
  font-style: normal;
  font-weight: 400;
  src: local("Inter"), local("Inter-Regular"), local("Inter Regular"),
    url("../assets/typography/Inter-Regular.woff2") format("woff2");
}

Browser Extensions and Font Loading

However, during testing I discovered something unexpected about how browsers handle fonts injected by extensions. I have Grammarly installed and will use this as an example. I already knew and expected that browser extensions inject their own @font-face declarations into pages. What I was not expecting was that these extension-provided fonts are treated as valid sources even when we explicitly use local() to reference a font.

In Firefox (Zen), I could see the extension-injected font source used as the source for the Inter font family.

@font-face {
  font-family: Inter;
  src: url("moz-extension://95811d4c-a043-46fe-b962-ab7accea9da2/src/fonts/1258fcda79d43bef/inter_Inter-Regular.woff");
}

In Chrome I could see that it was also using the extension-provided font source because it shows that the font origin was a network resource. I can also see the Inter font being loaded by utilizing the network panel. Odd that it would utilize a network resource when explicitly using local(), but it could be that Chrome considers an extension-provided font local but displays it as a network resource in the fonts developer tools panel.

With fallbacks

What if I specified a list of fallbacks in case Inter was not available? I tested this by adding a fallback font stack to the CSS:

h1,
p {
  font-family: Inter, system-ui, sans-serif;
}

My expectation was that, seeing that Inter was not installed locally, the browser would fall back to the system font. But… no difference. Both Firefox and Chrome continued to use the extension-provided font source, even when the Inter font was not installed locally.

Going Incognito

I decided to switch to Incognito mode as none of my extension will run in this mode. I tested two scenarios:

And… the results were as expected. In both cases, the browser fell back to the default browser front or the system font (when fallbacks were specified) when Inter was not installed locally. Some sanity has been restored. This should then also be the behavior when users have no extensions installed or none of the extensions injects a font that matches the local() reference you are using.

With Inter Installed Locally

What happens if we restore our CSS to the original state and install the Inter font locally? I installed the Inter font locally and removed the fallbacks from the CSS.

@font-face {
  font-family: Inter;
  src: local("Inter");
}

h1,
p {
  font-family: Inter;
}

The results are that both Firefox and Chrome used the font injected by the Grammarly extension. What if I specify those fallbacks again? Same result in both Chrome and Firefox (I also downloaded Firefox Developer edition to be sure). This leads me to believe that both Chrome and Firefox considers fonts injected through a browser extension as local fonts. This is tru even though Chrome continues to mark these as a network resource.

Key Takeaways

This investigation revealed several important points about working with local() in @font-face:

  1. A single local() reference can access all variants of an installed font family
  2. Browsers intelligently handle weight and style selection based on CSS properties
  3. Browser extensions can effectively “polyfill” fonts, making them available even when not installed locally

Best Practices

Based on these findings, here are some recommendations:

This is the web, it is wild and wonderful. Do not be too previous about how a user will see and experience your website or application and be okay with differences and code defensively to avoid surprises for you and your users. For things that are critical, test, test, and test again. — and do not forget Incognito mode.

I would love to hear your thoughts on this or any other font related topics. There is also an interesting thread I started on Mastodon with great additional reading around fonts on the web. Above all, have fun, make beautiful things, and keep the web open and accessible for all.