Private betav0.1.0
Docs
Format developer documentation

Fonts

Format's built-in font families, generic font resolution, and how to register a custom font-face.

Built-in font families

Format's base stylesheet sets sans-serif as the page's default font-family. The keywords sans-serif, serif, monospace, emoji, and math resolve to Format's built-in families, available on every page regardless of which Layout rendered it:

Generic familyFormat familySource typeface
sans-serifFormat SansAdwaita Sans
serifFormat SerifFMT Crimson Pro
monospaceFormat MonoJetBrains Mono
emojiFormat EmojiNoto Color Emoji
mathFormat MathSTIX Two Math

Generic families in CSS map to different fonts on every platform. For example, sans-serif on Linux is not the same on macOS or Windows, and this can shift again under a different browser's user agent stylesheet. To combat this variance, the built-in font families above are universally applied across every environment.

The Format-prefixed names in the middle column are durable identifiers and stay constant across releases. The source typeface on the right is an implementation choice that may evolve over time; a document using Format Serif keeps resolving even if the source is replaced.

Emoji glyphs render automatically in text styled with any of the built-in families. Characters outside the built-in families' coverage render as tofu boxes rather than substituted from elsewhere. See Missing glyphs and Format Tofu for the full mechanism.

Other CSS generic keywords

CSS defines additional generic family keywords beyond the ones above. All of the remaining keywords resolve to the built-in families like so:

KeywordResolves to
ui-sans-serifFormat Sans
ui-roundedFormat Sans
system-uiFormat Sans
ui-serifFormat Serif
ui-monospaceFormat Mono
cursiveFormat Serif
fantasyFormat Serif
fangsongFormat Serif
Useful

The keywords cursive, fantasy, and fangsong each name a face style (script, decorative, Chinese 仿宋) that no Latin serif can stand in for. Format resolves all three to Format Serif as a placeholder so a document referencing them still renders, but the recommended pattern is to register a custom face with @font-face and name it explicitly in the font-family chain rather than rely on the keyword.

Registering a custom font

To use a font beyond the built-in families, register it with @font-face. It works the same way it does on the web, with one Format-specific rule: declarations are adopted globally across the document. A face declared in one Layout's stylesheet is available in every other Layout in the same document. The same applies to declarations in shared stylesheets imported from the document entry point.

@font-face {
  font-family: "Inter";
  src: url("./fonts/Inter-Variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
}

The src URL resolves against your assets bundle. See Assets for how Format finds the file. When authoring in Studio, the typical workflow is to import a font package (Google Fonts or Fontsource) rather than ship the file by hand; the bundler emits the @font-face declarations and copies the font files into the bundle for you. See Studio fonts.

Once registered, apply the font by name on any element or class:

.heading {
  font-family: "Inter";
}

To apply a custom font across a whole category of text rather than a single rule, override the generic family it should replace.

Customizing the generic keywords

The generic keywords resolve through CSS variables on the page :host. To change what a keyword resolves to, set the corresponding variable:

VariableDefaultAffects keywords
--font-sansFormat Sanssans-serif, ui-sans-serif, ui-rounded, system-ui
--font-serifFormat Serifserif, ui-serif, cursive, fantasy, fangsong
--font-monoFormat Monomonospace, ui-monospace
--font-emojiFormat Emojiemoji
--font-mathFormat Mathmath
:host {
  --font-serif: "Besley";
}

Every element that uses any keyword in the Serif category, including Format's own base styles, now renders in Besley.

A single variable controls the whole category because, before rendering, the engine rewrites every generic keyword in the document's styles to read from its category variable. Write font-family: serif as normal; the override takes effect through the variable system.

This replaces the default family entirely: a glyph Besley doesn't carry has no broad family behind it. See Picking a fallback stack to keep one.

Override the --font-* properties only when you want the keyword's meaning to change across the document. For a one-off element in a different face, set font-family directly on the element.

Picking a fallback stack

A font-family value is a chain: the browser tries each family left to right and, for every character, uses the first that carries a glyph for it. A font that covers your primary language may not cover every character in your document, so end the chain with a family that does.

The simplest robust ending is a generic keyword, which resolves through the variable system to the corresponding built-in family:

.heading {
  font-family: "Charter", serif;
}

There's no need to name a Format family ("Format Serif", and so on) explicitly; the keyword already routes to it.

The same applies when you override a generic family. A bare override replaces the default, leaving nothing broad behind your font:

:host {
  --font-serif: "Besley";
}

End the value with the generic keyword to keep the built-in family as a fallback:

:host {
  --font-serif: "Besley", serif;
}

Now any glyph Besley lacks is served by Format Serif, which covers a far wider range of characters.

If your document includes content outside a single family's coverage, for example multiple scripts, symbols, or mixed languages, build the stack deliberately so every character is covered by a real font. Any character still left uncovered renders as a placeholder box (see Missing glyphs and Format Tofu).

Missing glyphs and Format Tofu

A browser falls back to system fonts for any character the font-family chain doesn't cover, with the result varying by operating system. On the web, that flexibility is a feature; in a PDF, it's a defect.

Format closes this gap with a sixth built-in family: Format Tofu. It carries the same tofu glyph for every codepoint (the empty rectangle that conventionally highlights a missing glyph). The engine appends Format Tofu to the end of every font stack, so no character can fall through to a system font. Anything the author's fonts and Format Emoji don't cover renders as a visible tofu box, identically, in Studio and your final PDF.

The result is that a missing glyph is made visible to the author in Studio rather than substituted silently in production. A tofu box anywhere in the preview marks a codepoint the document's font-family chain doesn't cover. The fix is to extend the chain with a font that covers the missing characters; see Picking a fallback stack.

How your fonts resolve

Every font-family (and font shorthand) in the document is rewritten before rendering so that two fallbacks are appended automatically, in this order:

Your stack + Format Emoji + Format Tofu
/* A declaration written as: */

.label {
  font-family: "Inter", sans-serif;
}

/* ...will be resolved to: */

.label {
  font-family: "Inter", var(--font-sans, var(--font-sans-system)), var(--font-emoji, var(--font-emoji-system)), var(--font-tofu-system);
}

This is why every stack in this guide stays safe without your writing it: your fonts come first, Format Emoji fills in emoji codepoints, and Format Tofu catches anything left over. You don't write the emoji or tofu fallbacks yourself; the engine adds them automatically.

Format Tofu is the one built-in family with no public override variable, it is a detection signal, not a typeface choice. However, the rest each have a public --font-* variable you can set.

System fonts

Fonts installed on your machine aren't available in the rendering environment. A system font name resolves in Studio's preview, where the font is installed, then falls through to tofu in the final PDF, where it isn't, so the document silently renders differently from what you designed.

Wrong
/* "Arial" is installed locally, so both of these look right in
   preview and render as tofu in the PDF. */
p {
  font-family: "Arial";
}

:host {
  --font-sans: "Arial";
}
Right
documents/invoice/styles.css
/* Ship the web font as an asset and reference it by name. */
@font-face {
  font-family: "Arial";
  font-style: normal;
  font-weight: 400;
  src: url("./arial.woff2") format("woff2");
}

:host {
  --font-sans: "Arial";
}

Before bundling a font, check its license permits embedding for how you distribute your documents.

Was this page helpful?