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.
- A single
@font-face
declaration usinglocal("Inter")
was sufficient to access all installed variants of the font family. - The browser automatically selected the appropriate font weight or style based on the element’s computed styles.
- Using Chrome’s developer tools, I could see:
- For the
h1
: PostScript name: Inter-Bold - For the
p
: PostScript name: Inter-Regular
- For the
- Using
font-weight
orfont-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:
- Inter font not installed locally and no fallbacks specified.
- Inter font not installed locally and fallbacks specified.
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
:
- A single
local()
reference can access all variants of an installed font family - Browsers intelligently handle weight and style selection based on CSS properties
- Browser extensions can effectively “polyfill” fonts, making them available even when not installed locally
Best Practices
Based on these findings, here are some recommendations:
- When using
local()
, a single reference to the font family name is sufficient if you’re targeting system fonts - Test font loading in a clean environment (like Incognito mode) to avoid extension interference. This is particularly important when testing how font loading will impact users over slow networks, for those who are blocking web fonts, and users using data saver modes which often block web fonts. Read the article from Sentry on Cumulative Layout Shift (CLS) and web fonts.
- Windows has a feature for enterprises to block untrusted fonts.
- Some users will block fonts loaded from Google Fonts due to GDPR.
- Some users will block font loading for security reasons such as fingerprinting - also see the related reading on this page. Also, quoting from MDN Web Docs: “While the set of preinstalled fonts is likely to be the same for all users of a particular device, the set of user-installed fonts is not. By discovering the set of user-installed fonts, a site can therefore build a fingerprint for the device, helping the site to track users across the web. To prevent this, user agents may ignore user-installed fonts when using
local()
.”
- Always provide fallbacks. Refer to either system font stack or modern font stacks for examples and guidance.
- Be aware that browser extensions might affect your font loading strategy in ways you didn’t anticipate
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.