Leveraging System Fonts on the Web
Blog post draft: the reason they called it
font-family
in CSS was because its like many familiesâdysfunctional. (@jimniels)
I donât even remember why, but the other day I was thinking about Appleâs âNew Yorkâ font and wondering how you could tell browsers (on Apple devices) to render text with that font. I know all about using the system font stack, but in the case of macOS the system font would be Appleâs sans-serif âSan Franciscoâ. What if I wanted to use this alternative serif font (âNew Yorkâ is Appleâs serif font that they initially showcased with the new Apple Books app)? Can you do that in CSS?
A quick search with the right keywords led me to this same question on StackOverflow, which led me to the CSS fonts spec, which later led me to Chris Coyierâs system fonts snafu post. All interesting reading. And to top it all off, while I was writing this blog post, I happened to watch Appleâs WWDC20 video detailing whatâs new in Webkit, which introduces all the stuff Iâm about to write about.
All of this reading got me thinking, and all of that thinking got me writing, and all of that writing got me publishing, and all that publishing got you, dear reader, here reading this post.
tldr: the CSS fonts spec has a couple (new to me) generic font families, like ui-serif
and ui-sans-serif
, aimed at providing finer-grained controls for specifying OS-level fonts. This allows developers the power to integrate their UIs with the look and feel of the underlying operating system. To suggest Apple user agents render text on screen with the âNew Yorkâ serif font, developers can specify: font-family: ui-serif
.
Diving Deeper
First, I was a bit confused just like Chris was:
When I first heard of using system font stacks, there was
-apple-system
andBlinkMacSystemFont
...Then came along-system-ui
[which was] obviously less Mac-specific. But there is alsosystem-ui
(no starting dash), and that seems to do the same thing and Iâm not sure which is correct. Now it looks like the plan isui-sans-serif
and friends...I like the idea, but Iâd love to hear clarity from browser vendors on what the recommended use is.
He then asks if, given the history behind specifying system fonts, weâre in a place like this for leveraging system fonts:
body {
font-family:
ui-sans-serif,
system-ui,
-system-ui,
-apple-system,
BlinkMacSystemFont,
Roboto, Helvetica, Arial,
sans-serif,
"Apple Color Emoji";
}
If Chris over at CSS-Tricks doesnât know the answer, then I suppose weâre all a bit lost.
The question in my head was: why yet another way to specify a generic font family? My mental model for fonts in CSS has always been: specify a couple specific fonts, then provide fallbacks to generic fonts. So if you wanted the system to use its sans-serif font, why not have that be your font declaration, i.e. sans-serif
?
In other words, whatâs the difference between this:
body {
font-family: sans-serif;
}
And this?
body {
font-family: ui-sans-serif, sans-serif;
}
In order to answer that question, I had to read the CSS fonts spec for font-family to correct my mental model.
Specific and Generic Families
In CSS, you have the font-family
property and then a value, which according to the spec can be a <family-name>
or a <generic-name>
. A family name would be something like Helvetica
while a generic name would be something like sans-serif
. Once you have one or more family family/generic names declared, the browser tries to map every character of text to one of the fonts specified:
A user agent iterates through the list of family names until it matches an available font that contains a glyph for the character to be rendered. This allows for differences in available fonts across platforms and for differences in the range of characters supported by individual fonts.
The idea here is actually quite a powerful one. Rather than having binary error handling (âdisplay this text in Helvetica or donât display any text at allâ) CSS provides developers broad expressiveness in handling errors related to the absence of fonts. The main idea is that, with CSS, you can express stylistic suggestions for font usage on a continuum from specific to generic. Itâs a rather robust way to preserve the spirit of design choices across the multitude of platforms that access the web.
Thatâs great and all, but it still doesnât answer my question: why canât you say sans-serif
and the operating system will leverage itâs sans-serif system font (like San Francisco on macOS)?
Different Types of Generic Families
There are different types of <generic-name>
families that can be used in CSS. Hereâs an enumerated list according to the spec:
serif
*sans-serif
*monospace
*system-ui
cursive
fantasy
emoji
math
fangsong
ui-serif
âui-sans-serif
âui-monospace
âui-rounded
â
The family names with a *
are caveated with this note in the spec:
Note: [these] must always map to at least one matched font face. However, no guarantee is placed on the character coverage of that font face. Therefore, the font [these are] mapped to may not end up being used for all content.
While the family names with a â
are caveated thus:
Note: [these are] not expected to map to any font on platforms without an appropriate system font.
As you can see, some generic families must map to a particular font on the user agent (like sans-serif
) but others donât necessarily have to (like ui-sans-serif
). This is a crucial difference between the types of generic font families you can specify in CSS.
A generic font family is a font family which has a standard name (as defined by CSS), but which is an alias for an existing installed font family present on the system...Different generic font families may map to the same used font.
Note that last sentence: âdifferent generic font families may map to the same used font.â That is precisely what is happening in Chrisâ example:
body {
font-family: ui-sans-serif, system-ui, sans-serif;
}
All three of those may map to the same font, but they also may not. It depends on a combination of configurations across both the operating system, the browser, and the userâs preferences at both levels. That is but a taste of the kind of robust expressiveness CSS gives developers when dealing with font choices.
Ok so, now weâre getting closer to the answering my question: why canât I say sans-serif
to use the systemâs sans-serif font? Given the above context, you can see that if ui-sans-serif
and system-ui
are not valid, recognized generic names on the user agent, the system will fallback to sans-serif
which, according to the spec, must map to some font on the userâs system. But which font? If thereâs no âsystemâ font, what font does the system use?
User Preferences and Generic Families
As noted, generic families exist on a kind of continuum, from families that may map to a particular font to families that must map to a particular font. These vary from one user agent to the next.
Generic font families are intended to be widely implemented on many platforms, unlike arbitrary
<family-name>
s which are usually platform-specific names. [Generic families] are expected to map to different fonts on different platforms.
When you specify a <generic-name>
that must map to particular font, like sans-serif
, youâre telling the system âfind something that roughly fits the typographic style of a sans-serif fontâ. Which font gets used in that scenario is, however, ultimately left up to the browser and not the developer.
User agents should provide reasonable default choices for the generic font families, that express the characteristics of each family as well as possible, within the limits allowed by the underlying technology. User agents are encouraged to allow users to select alternative faces for the generic font families.
That last part is worth noting: browsers can, in fact they are âencouragedâ, to allow users to set their own preferences for generic families. The browser can set a default by mapping sans-serif
to, say, âArialâ (it could, I suppose, even try to map sans-serif
maps to âSan Franciscoâ but does not AFAIK). This default choice is left up to the browser.
However, as the spec says, the user should have the ability to override what sans-serif
maps to. This means, as a developer, when you specify sans-serif
, that might result in âArialâ on one user's computer (depending on the browserâs default preferences for sans-serif
) but it could also result in, say, âHelveticaâ on somebody elseâs computer (assuming they changed their browserâs default preferences). Hereâs an example screenshot of a userâs font settings in Google Chrome:
From this we see that generic families which must map to a particular font are configurable by the end user.
So, going back once again to my own question: why canât I say sans-serif
and have that map to the system font (San Francisco on Apple devices)? The answer is: ui-sans-serif
gives CSS authors extra specificity in declaring what fonts the browser should use. If, as a developer, you want to express âuse the operating systemâs sans-serif font over the userâs specified preference of a sans-serif fontâ then you need more fine-grained <generic-name>
families than just sans-serif
. Example:
/* This means use the browser's default sans-serif, which
might be configured by the user */
body {
font-family: sans-serif;
}
/* This means _first_ use the system sans-serif, then if that
doesnât map to anything (or isnât supported), use the
browserâs default sans-serif, which may be user configured */
body {
font-family: ui-sans-serif, sans-serif;
}
A Cascade of Generic Families
Letâs dive even deeper. Let's look at system-ui
for a moment:
This generic font family lets text render with the default user interface font on the platform on which the UA is running. A cross-platform UA should use different fonts on its different supported platforms. The purpose of âsystem-uiâ is to allow web content to integrate with the look and feel of the native OS.
Ok that makes sense. If I want to San Francisco to render on Apple devices, I can say system-ui
because San Francisco is the system font in those cases. It just so happens that San Francisco is a sans-serif font. It is very much possible that another operating system out there might have serif font as the base system font. Heck, itâs even possible system-font
could map to Comic Sans. It all depends on the operating system. This is where ui-sans-serif
and ui-serif
come into play and provide CSS authors more control. They allow you to differentiate between âuse the systemâs fontâ (which could be a serif font, a sans-serif font, a monospace font, etc.) and âuse the systemâs serif fontâ.
In that light, the difference between system-ui
and ui-sans-serif
is completely dependent on the user agent. On some UAs they might map to the same font. On others they might not. In the case of Apple UAs and Safari (at the time of this writing), system-ui
and ui-sans-serif
map to the same font: San Francisco. But system-ui
and ui-serif
are not the same because the âsystem fontâ is not a serif font on Apple UAs. In that case, ui-serif
is what allows you to specify the âNew Yorkâ serif font.
Again, if I revisit my original question: what's the difference between ui-sans-serif
and sans-serif
? Itâs a matter of specifying intent. sans-serif
means âI suggest you, computer, use a sans-serif font family. You choose whatâs best (or leverage the userâs choice in settings).â ui-sans-serif
is saying âI suggest you, computer, use the systemâs sans-serif font, which is a very specific thing in and shouldnât take into account a userâs browser settings.â
Ok, so given everything discussed in this post, letâs play this out in the example Chris gave:
/* Specifying a font, by level of priority: */
body {
font-family:
/* use the sans-serif font that the operating system
classifies as a sans-serif font for native apps */
ui-sans-serif,
/* use the font the operating system uses for native
applications, may be serif, may be sans-serif, may be
something else (there are, as you can see, a couple
different ways to specify this) */
system-ui,
-system-ui,
-apple-system,
BlinkMacSystemFont,
/* use one of these specific font families if present */
Roboto, Helvetica, Arial,
/* use a sans-serif family as determined by the browser
which may or may not be a configurable option for
the user to override */
sans-serif,
/* use emojis */
"Apple Color Emoji";
}
I believe thatâs roughly how this would all shake out.
Final Thoughts
So how does one know what something like system-ui
means on each platform? I suppose that would just take research. Research that I cannot do because I do not have the device inventory to do so (or, honestly, the time or desire). But it would be nice if there was a dictionary of this somewhere, i.e.
- macOS, iOS iPadOS, etc.
ui-sans-serif
: San Franciscosystem-ui
: San Franciscoui-serif
: New Yorkui-monospace
: SF Mono
- windows
- ???
- android
- ???
All of this does make me wonder: does leveraging the system font give you free OS-level optimizations in font display? For example, the San Francisco family has âSF Pro Displayâ and âSF Pro Textâ, both of which were designed with optimizations in mind based on font sizing. Supposedly, if youâre doing native app development right on an Apple device, the OS takes care of using the right font based on your font size. Does this happen for browsers rendering text too? For example, if I say ui-sans-serif
does that map to âSF Pro Displayâ or âSF Pro Textâ? Or is it dependent on the size at which the font is displayed? I donât know the answer to that question. And obviously itâs very Apple-device specific. But presumably if you were accessing the web on any other device, it presumably could have all kinds of optimizations like this built in that you donât know about.
Ok, phew. That was a lot of words. I hope A) you found something useful in here and B) itâs actually correct. Writing this blog post help me clarify my thoughts and reshape my mental model for font families in CSS. All of that said, this was an exercise in me reading the spec and attempting to articulate how the mechanics of the specâs declarations play outâparticularly on Apple devices.
Really, this is all a kind of âpeering under the hoodâ at the complexity that lays between the operating system, the browser running on that operating system, and the generic-to-specific continuum of controls the browser tries to provide to CSS authors in spite of the almost infinite lack of surety about anything involving the environment in which someone might be viewing your web page.
Update 2020-12-03
From webplatform.news I found a link to this post from Chris Siebenmann which talks about how Firefox clues you in a little bit more about how it handles defaults:
Firefox is...telling you what font [a family name] actually maps to. If you go into "Advanced..." and have not customized your font choices, you can see what all three of the magic names map to.
I looked at this myself and its pretty neat. The settings pane clear lays out how Firefox is communicating its defaults and what those actually map to:
Whatâs cool is to see how the font family default mappings change as you change the language:
Now what would be really neat is if it had a couple more settings panes (these ones non-configurable because you canât change the system font, as noted in this article) that showed you what the system font mapped to. If youâre on a Mac, itâs probably well known what those settings would be. But if you were on some flavor of linux, who knows what it would be!