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 reads your repository, notices which of them you actually apply, downloads those and extracts them into your repository. Apply one, 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 walks your repository — and the builder’s own shared templates — looking for the bundled families you apply. Three spellings count: a utility class (font-inter), a token reference (var(--font-inter)), or the family name itself as CSS writes it (font-family: "Inter", or font-['Inter',sans-serif] in a Tailwind arbitrary value). Templates, stylesheets, JSON and JavaScript files are read in full, with CSS comments stripped; a Markdown page is read only for its frontmatter and its {…} attribute blocks, with fenced code removed first. For every family 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: apply the font, 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. Naming a font in the prose of a page does not count — a documentation page that talks about font-geist does not install Geist. If a bundled family’s token reaches the compiled CSS but nothing applies it, the build logs an info line saying it was not installed and why; reference the family from your own CSS or a template if that is wrong.
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: Free by default, your own Pro if you have it
Every Leed site compiles against Font Awesome 7. By default that is Font Awesome Free (currently 7.3.1): its Solid and Brands icons, plus Free’s smaller Regular set. The icon classes are available in your Handlebars templates and in the icon syntax of Leed Markdown, and there is no CDN to add. If you own a Font Awesome Pro license, you install your own Pro fonts with one command and Leed supplies the matching styles.
Unlike a text face, the icon fonts are never downloaded by the build — leed site build installs no Font Awesome at all. The .woff2 files reach src/static/webfonts/ only through an explicit install, which records the edition and version in src/static/webfonts/fa-version.txt; the build reads that file to choose the matching stylesheet. A site with no fa-version.txt compiles against the newest Free release, and its icons render empty until fonts are installed.
| To | Run | What it does |
|---|---|---|
| Install Free | leed site init --font-awesome | Downloads Font Awesome Free into src/static/webfonts/. Does nothing when Font Awesome fonts are already there, so it can never overwrite Pro. leed setup runs it for you |
| Install or upgrade your own Pro | leed site init --font-awesome-pro <zip> | Unpacks the webfonts from the Pro web package zip you downloaded from fontawesome.com, such as fontawesome-pro-7.3.1-web.zip. Run it again with a newer zip to upgrade; the previous release’s files are removed |
In a site that is already set up, either flag installs the fonts and does nothing else. Commit the files afterward, as you would a text face. Leed carries a stylesheet for each supported release — Free 7.3.1, and Pro 7.0.0 and 7.3.1 today — and refuses a Pro zip of any other version, listing the supported ones. Pro+ installs as Pro. A site whose fa-version.txt holds only a version number, the format from before Free became the default, is read as Pro at that version and builds unchanged.
The style utilities are ordinary Tailwind utilities, and knowing that matters for the alert-icon override described on Theming Alerts. The weights — fa-solid and fa-regular on every site, plus fa-thin, fa-light and fa-semibold on Pro — set --fa-style; the families — fa-brands, and on Pro fa-duotone, fa-sharp and families such as fa-utility, fa-slab and fa-vellum — set --fa-family; 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.
Leed’s own icons
Every icon Leed’s templates and components draw — the menu, search, chevrons, copy button, spinner, the five alert icons and the rest — is a Free icon, set through a named leed-icon-<role> utility that reads --site-icon-<role> before Leed’s default. To change one, set the token in your own :root; no template edit is needed. The full list is on Site Token Contract. On a Pro site, the build also adds a small stylesheet that gives those icons back the Pro styles and glyphs they had before Leed switched to Free-safe icons; a --site-icon-* value still wins there.
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.