Fonts

Overpass is the default, not a requirement. A site that wants it declares nothing: the shared sheet already serves it. A site with its own brand face declares it in one file and the build does the rest.

Declaring a face

# src/data/fonts.yml
sans:
  family: Sohne
  source: src/fonts/sohne-var.woff2
  licence: src/fonts/sohne-licence.txt
  weights: 100 900        # a range makes it variable; one file covers all of it
  subset: content         # content | latin | none
  preload: true
mono:
  inherit: true           # keep the shared sheet's Overpass Mono

Two slots, sans and mono, mapping to --fp-sans and --fp-mono. Anything not declared, or marked inherit: true, keeps what the shared sheet provides.

Originals live in src/fonts/, beside src/media/, because both are sources the build processes. They do not go in src/assets/, which is copied to the output verbatim: an original left there would ship alongside the subset meant to replace it. The build refuses that rather than letting it happen quietly.

The four rules

Self-hosted, always. source: is a path inside the repo. A face linked from a third-party font host is not accepted. That is a privacy position and a performance one at once, and it keeps every page's head pointing at origins the project controls.

A licence on file, or no build. licence: must point at a real file, committed beside the font. This is a build failure, not a lint. The OFL sitting next to Overpass on the CDN is the pattern.

Shared faces belong on the CDN. The rule the component catalogue already follows: a face appearing on a second site moves to fivepaths-cdn under a version path, so both sites get the same bytes and the same cache entry.

Fonts count against the budget. File count and total bytes, in .cms/budgets.json. The shared sheet spends two files; a site adding more should be able to say why. See BUDGETS.md.

Subsetting

Subsetting
Mode Keeps Use when
`content` (default) Every character the built site renders, plus ASCII and common punctuation Normal. The smallest result
`latin` A fixed Latin range, roughly U+0020 to U+024F The site will gain copy outside the build, and you would rather the font file not change with the text
`none` Everything in the source file You have a reason

content reads the rendered HTML, so the subset is always in step with the copy. Overpass on the kitchen-sink fixture goes from 38.5 KB to 27.2 KB that way, and a face with wider language coverage falls much further.

Under translation, subsetting is per locale, so a site adding Traditional Chinese does not make its English pages carry the glyphs. See TRANSLATION.md.

What the build writes

public/assets/fonts/sohne.<hash>.woff2     the subset, content-addressed
public/assets/fonts.<locale>.css           @font-face plus the token override

One stylesheet per locale, because subsetting is per locale. The hash covers the subset itself, so two locales whose text needs the same glyphs produce the same file and share it; a Latin-only site therefore pays nothing for the per-locale machinery. Only the faces a build actually references are copied to the output, not everything the cache has accumulated.

fonts.css is generated and says so in its first line. To change a face, edit fonts.yml; editing the stylesheet is overwritten on the next build.

The head gets a preload for every face marked preload: true, which should be the one face first paint needs and rarely more than one.

Two passes, and why

Content subsetting needs the rendered text to know which glyphs to keep. The preload link needs the resulting font's content hash. So the build renders, subsets, then renders again with the font known. The first pass is never written anywhere and costs about twenty milliseconds.

If you are reading a stack trace and wondering why rendering happens twice, that is why.

Checking it

cms check

reports font file count and total bytes against the budget. The check also verifies the declared face actually loaded rather than falling back silently, which is the failure that survives review because the page still looks right to whoever has the font installed locally.

Next

Continue with budgets.