You do not host fonts on a Leed site, and you do not go and find them. Six families ship with the site builder; the build watches the CSS it just compiled, notices which of them you actually referenced, downloads those and extracts them into your repository. Reference a token, build once, and the .woff2 files are sitting in src/static/webfonts/ waiting to be committed.
Everything on this page is free on every plan. The one exception is scoped and called out at the bottom: typing a custom font name into the CMS is a Starter feature. Adding a @font-face and using it in your own templates is not gated at all.
The six bundled families
| Token | Family | Kind | In the CMS dropdown? | Installs into | Package |
|---|---|---|---|---|---|
--font-noto-sans | Noto Sans | Sans | Yes | src/static/webfonts/noto-sans/ | noto-sans-2.013.zip |
--font-open-sans | Open Sans | Sans | Yes — and the default | src/static/webfonts/open-sans/ | open-sans.zip |
--font-inter | Inter | Sans | Yes | src/static/webfonts/inter/ | inter-4.1.zip |
--font-geist | Geist | Sans | Yes | src/static/webfonts/geist/ | geist-1.7.2.zip |
--font-geist-mono | Geist Mono | Mono | Yes | src/static/webfonts/geist-mono/ | geist-mono-1.5.1.zip |
--font-jetbrains-mono | JetBrains Mono | Mono | Yes | src/static/webfonts/jetbrains-mono/ | jetbrains-mono-2.304.zip |
All six ship with a token, an @font-face file and a package entry, and all six are offered in the CMS font dropdown. font-open-sans is what a documentation set uses when nothing else is chosen.
Registration is three coupled things
This is the mental model that makes every font question on this page answerable. A usable font is three separate registrations that happen to share a name:
- A
--font-<name>token in an@themeblock. This is what makesfont-<name>a real Tailwind utility. Leed’s six live inleed.css. - An
@font-faceblock, infonts/<family>.css, aggregated byfonts/all.css, pointing at/static/webfonts/<family>/<Family>-<Weight>.woff2. - An entry in
fonts/config.jsonc, naming the archive the auto-installer downloads.
Miss one and the failure looks different every time, which is why it is worth knowing which of the three you are missing:
| Missing | Symptom |
|---|---|
The @theme token | font-<name> is not a valid utility. Tailwind emits nothing for it and the class is inert — no error, no rule. |
The @font-face | The utility exists and applies, and the text renders in the next family in the fallback stack. Looks like “the font didn’t load”, because it didn’t. |
The config.jsonc entry | The .woff2 files never arrive. Same visible result as the previous row, from a different cause. |
How a font installs itself
After every successful Tailwind compile, the build scans the compiled CSS for --font-* custom properties — --font-weight-* is excluded, so weight scales do not trip it — and looks each match up in config.jsonc. For every match it finds, if <repo>/src/static/webfonts/<dir>/ is not already a directory, it fetches the family’s archive from static-assets.leed.ai, extracts it there, and deletes the archive.
The practical consequence is short: reference the token, build once, and the files appear. Use font-inter anywhere in your CSS or templates, run leed site build, and Inter’s woff2 files are on disk.
A family that exists in the config but is never referenced is never downloaded. That is deliberate: the six families together are far larger than any one site needs.
Where the @font-face rules come from
fonts/all.css imports one stylesheet per family, and each of those declares every weight and style the family ships, in both roman and italic where available:
@font-face {
font-family: "Noto Sans";
font-style: normal;
font-weight: 100;
font-display: swap;
src: url(/static/webfonts/noto-sans/NotoSans-Thin.woff2) format("woff2");
}Every face carries font-display: swap, so text paints in the fallback family immediately and re-renders when the webfont arrives, rather than sitting invisible.
Font Awesome on every site, with no setup
Leed loads Font Awesome 7 on every site it builds. There is nothing to install, nothing to configure and no CDN to add — the icon classes are simply available, in your Handlebars templates and in the icon syntax of Leed Markdown.
The build handles the font files the same way it handles a text face, with one extra step: it writes the installed version to src/static/webfonts/fa-version.txt and compares against it on every build, so a version bump in the builder re-downloads the faces once and then stops.
All the style utilities are present, and knowing that they exist as utilities matters for the alert-icon override described on Theming Alerts: fa-thin, fa-light, fa-regular, fa-solid, fa-duotone, fa-sharp and fa-brands. A style utility sets --fa-style; a glyph utility such as fa-circle-info sets --fa to a codepoint. They are plain custom properties, which is why applying a second pair replaces the first rather than stacking with it.
Because Font Awesome’s utilities are ordinary Tailwind utilities, they are tree-shaken like everything else. An icon class named only inside a JSON data file under src/ still emits, because that file is scanned as text — an icon named only in the CMS company record and never published into a file under src/ does not.
Adding your own font
Three steps, in this order.
1. Put the .woff2 files in your repository, under src/static/webfonts/<your-family>/. That is the same directory the auto-installer writes to, so your fonts and Leed’s sit together and are served by the same rule.
src/
static/
webfonts/
my-brand/
MyBrand-Regular.woff2
MyBrand-Medium.woff2
MyBrand-Bold.woff22. Declare the faces in a file under tailwind/, imported from site.config.css:
/* tailwind/site/fonts.css */
@font-face {
font-family: "My Brand";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("../src/static/webfonts/my-brand/MyBrand-Regular.woff2") format("woff2");
}
@font-face {
font-family: "My Brand";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("../src/static/webfonts/my-brand/MyBrand-Bold.woff2") format("woff2");
}3. Register the token so the utility exists:
/* tailwind/site/theme.css */
@theme {
--font-my-brand: "My Brand", ui-sans-serif, system-ui, sans-serif;
}font-my-brand is now a usable class in your templates and @apply-able in your CSS.
How the URL resolves
You do not have to reason about where your CSS sits relative to the output. The build’s PostCSS chain rewrites font URLs: any url() whose pathname contains webfonts/ becomes /static/webfonts/<everything after that>, so url("../src/static/webfonts/my-brand/MyBrand-Regular.woff2") and url("./webfonts/MyBrand-Regular.woff2") both land on /static/webfonts/my-brand/MyBrand-Regular.woff2 in the compiled stylesheet. A URL that matches neither webfonts/ nor images/ is passed through untouched, so an absolute /static/... path also works and simply skips the rewrite. The same step is described from the build’s side on Tailwind Build.
Everything under src/static/ is served from /static/, which is why the published path has no src in it. The caching behavior of that directory is on Static Files and Caching.
Overriding the default mono font
There is one token for monospace, and every font-mono rule in Leed’s documentation CSS reads it:
@theme {
--font-mono: "My Brand Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}Set that in your own @theme block and inline code, code blocks, tables, keyboard keys and the whole API reference follow at once.
The reason this deserves its own section is the shape of the bug you get by not doing it. It is tempting to put a brand mono on the .hljs rules inside a custom code theme, because that is where you are looking when you notice the font. Do that and fenced code blocks change while inline `code`, table cells and the API reference stay on the system stack — a mismatch subtle enough to ship and annoying enough that somebody eventually files it. One token, not a set of rules.
Using a custom font name for a documentation set
The CMS font dropdown offers six families. If you want a seventh — a face of your own — you type the name instead of picking one, and the CMS stores the full class (font-mybrand).
The bare name is validated as lowercase letters and digits only, at most 32 characters — no dashes — so font-mybrand is accepted and font-my-brand is refused with a 400 on every plan, before the tier is even consulted. The full rules, the gate’s exact responses, and the @utility half that makes the name mean something are on Custom Documentation Themes; the built-in catalogs as they appear in the CMS are on Themes, Fonts and Code Themes.
One thing a custom font name does not need is a theme. The font class is stamped onto <html> independently of the color theme, so font-mybrand plus a built-in color-theme-teal is a perfectly ordinary configuration.