How to generate native system font stacks for sans, serif, mono
- Step 1Pick the style — Use the **Style** select: `Sans-serif` for UI and body text (the default), `Serif` for long-form prose, `Monospace` for code blocks and tabular figures. The style maps directly to a CSS generic family and to the variable name in the output (`--font-system-sans`, `--font-system-serif`, `--font-system-monospace`).
- Step 2Pick the OS scope — Use the **OS** select. `All` (default) produces the full cross-platform chain — the right choice for a public website where you don't control the visitor's OS. Pick `macOS`, `Windows`, `Linux`, `iOS`, or `Android` only when you genuinely target one platform (a packaged app, an in-app WebView, a kiosk).
- Step 3Click Generate — Press **Generate**. There is no file to upload — this is a generative tool, so it runs instantly and shows the CSS in the result panel along with metrics: the chosen Style, the OS scope, and the number of fonts in the stack.
- Step 4Read the metrics to sanity-check the stack length — The result panel reports **Fonts in stack** — for example sans-serif / All is 10 entries, sans-serif / Android is 4. If a scoped stack looks short, that's by design: a single-OS stack drops the names that don't exist on that platform.
- Step 5Copy or download the CSS — Use **Copy to clipboard** for the snippet, or **Download** to save it as a `.css` file (named like `system-sans-serif-all.css`). The output already contains the `:root` variable and a `body` rule that references it — paste it near the top of your global stylesheet.
- Step 6Apply the variable elsewhere — The generator only wires `body`. To use the same stack on other elements, reference the variable yourself: `code { font-family: var(--font-system-monospace) }`. Run the generator once per style if you need sans, serif, and mono variables side by side, then keep all three custom properties in your `:root`.
The two controls and what they do
The entire UI is two select menus. There is no font-name input, no reorder handle, and no preset list — the stacks are fixed curated lists.
| Control | Values | Default | Effect |
|---|---|---|---|
| Style | sans-serif, serif, monospace | sans-serif | Chooses the curated family list and the CSS variable name (--font-system-sans / --font-system-serif / --font-system-monospace). The matching generic family keyword closes the list. |
| OS scope | all, mac, windows, linux, ios, android | all | all emits the full cross-platform chain; a single OS emits a trimmed chain containing only names that exist on that platform plus a generic-family fallback. |
Sans-serif stacks by OS scope (exact output)
These are the literal lists the generator emits for the default Sans-serif style. Quoting and order are reproduced verbatim from the tool.
| OS scope | Fonts in stack | Emitted font-family list |
|---|---|---|
all | 10 | -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", Arial, sans-serif |
mac / ios | 5 | -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif |
windows | 5 | "Segoe UI", Tahoma, "Helvetica Neue", Arial, sans-serif |
linux | 7 | Roboto, Oxygen, Ubuntu, Cantarell, "DejaVu Sans", "Liberation Sans", sans-serif |
android | 4 | Roboto, "Helvetica Neue", Arial, sans-serif |
Output anatomy
Every run produces the same two-part block. The variable suffix is derived from the style by stripping anything after the first hyphen.
| Style chosen | Variable emitted | Generic family tail | Body rule |
|---|---|---|---|
sans-serif | --font-system-sans | sans-serif | body { font-family: var(--font-system-sans); } |
serif | --font-system-serif | serif (not always last — see edge cases) | body { font-family: var(--font-system-serif); } |
monospace | --font-system-monospace | monospace | body { font-family: var(--font-system-monospace); } |
Cookbook
Copy-paste recipes for the common style + OS combinations, showing the exact generated CSS so you know what lands in your stylesheet.
Default: cross-platform sans-serif body font
ExampleStyle = Sans-serif, OS = All. This is the recipe most public sites want — the same chain GitHub, Medium, and Stack Overflow use for body UI. San Francisco resolves via the -apple-system/BlinkMacSystemFont keywords; Segoe UI on Windows; Roboto on Android and Linux.
/* Style: sans-serif, OS scope: all */
:root {
--font-system-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", Arial, sans-serif;
}
body {
font-family: var(--font-system-sans);
}
Metrics panel: Style sans-serif · OS scope all · Fonts in stack 10Monospace for code blocks (cross-platform)
ExampleStyle = Monospace, OS = All. Note this stack — and only the monospace stacks — leads with the ui-monospace generic keyword, which modern browsers resolve to the OS's UI monospace font. The sans-serif and serif stacks do NOT include a ui-* keyword.
/* Style: monospace, OS scope: all */
:root {
--font-system-monospace: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
body {
font-family: var(--font-system-monospace);
}
/* Re-target it at the elements you actually want monospaced: */
pre, code, kbd, samp {
font-family: var(--font-system-monospace);
}Three variables in one :root
ExampleThe generator wires only body, and only one style per run. To keep sans, serif, and mono custom properties together, run it three times and merge the :root blocks. This is the practical setup for a site with UI, prose, and code.
/* Merged from three runs (all OS scope) */
:root {
--font-system-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", Arial, sans-serif;
--font-system-serif: Iowan Old Style, Apple Garamond, Baskerville, "Times New Roman", "Droid Serif", Times, "Source Serif Pro", serif, "Apple Color Emoji";
--font-system-monospace: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
body { font-family: var(--font-system-sans); }
article { font-family: var(--font-system-serif); }
pre, code { font-family: var(--font-system-monospace); }Windows-only trimmed stack for a packaged app
ExampleStyle = Sans-serif, OS = Windows. When you ship an Electron app or a kiosk that only ever runs on Windows, the trimmed stack avoids carrying mac/Linux names that will never match. It leads with Segoe UI and falls back through Tahoma to Arial.
/* Style: sans-serif, OS scope: windows */
:root {
--font-system-sans: "Segoe UI", Tahoma, "Helvetica Neue", Arial, sans-serif;
}
body { font-family: var(--font-system-sans); }
Metrics panel: Style sans-serif · OS scope windows · Fonts in stack 5Pair with a fluid scale and font-display
ExampleA system stack handles the family; size and loading strategy are separate tools. Generate the stack here, then size body text with the clamp generator and, if you later add a brand web font, pick its loading strategy.
/* 1. This tool → family */
:root { --font-system-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", Arial, sans-serif; }
/* 2. clamp-font-size-generator → fluid size */
body {
font-family: var(--font-system-sans);
font-size: clamp(1rem, 0.958rem + 0.208vw, 1.125rem);
}
/* 3. font-display-strategy → only if you add a brand web font later */Edge cases and what actually happens
Every row below was probed against the live API. Some documented requirements (alphabetical axis order, numerical tuple order) are not actually enforced in practice — useful to know if you've been blaming the wrong thing for a 400.
There is no font-name input — you can't add your brand font
By designThe generator emits fixed curated lists; there is no field to prepend Inter or Brand Sans. To layer a brand font in front of a system fallback, generate the stack here, then hand-edit the front of the list, or use the Font Face Generator to declare the brand @font-face and put its family name first.
Stacks are not reorderable and there are no presets
By designThe UI is two selects, full stop — no drag handles, no checkbox list of fonts, no saved presets. The order is the curated preference order (Apple keywords, then Windows, then Android/Linux, then generic). If you need a different order, copy the output and edit it by hand.
The body rule only styles `body`
ExpectedOutput applies the variable to body only. Headings, code, and form controls inherit from body unless a UA stylesheet overrides them — and <input>, <textarea>, <select>, <button> notoriously do NOT inherit font-family in many browsers. Add input, textarea, select, button { font-family: inherit; } yourself, or reference the variable on those selectors.
Sans-serif and serif stacks have no `ui-*` keyword
ExpectedOnly the monospace stacks start with ui-monospace. The sans-serif stacks start with -apple-system; the serif stacks start with family names, not ui-serif. If you specifically want the ui-sans-serif/ui-serif generic keywords, add them to the front of the copied output yourself — the generator does not emit them.
Serif / All starts with unquoted multi-word names
QuirkThe serif / All stack begins Iowan Old Style, Apple Garamond, … with the first two names unquoted despite containing spaces. Per CSS, multi-word family names should be quoted; unquoted multi-word names are technically a parse risk. In practice browsers tolerate it, but if you want strictly valid CSS, quote them after copying, or use the macOS-scoped serif stack, which quotes "Iowan Old Style" and "Apple Garamond".
Serif / All ends with `serif, "Apple Color Emoji"`
QuirkIn the serif / All stack the generic serif keyword is followed by "Apple Color Emoji". Normally the generic family is the very last token. Here an emoji fallback trails it, which is harmless (the generic already guarantees a match) but means the last entry isn't the generic keyword. The single-OS serif stacks end cleanly with serif.
Single-OS stacks still keep a generic-family fallback
ExpectedEven the Windows or Android scoped stacks end with sans-serif / serif / monospace. So a Windows-scoped stack rendered on a Mac won't fail — Segoe UI/Tahoma simply won't match and it falls through to Arial then the generic. Scoping trims the list; it does not make the stack OS-exclusive.
Output is CSS only — not a Swift or Kotlin token
ExpectedThe download is a .css file with a :root variable. There is no JSON, no design-token, and no native-platform export. To turn the stacks into multi-platform tokens, see the companion guide Generate System Font Stacks Programmatically.
`-apple-system` does nothing on non-Apple browsers
Expected-apple-system and BlinkMacSystemFont are vendor keywords honoured by Safari and Blink-on-Apple. On Windows/Android they are simply ignored and the stack falls through to "Segoe UI"/Roboto. That's intended — the All stack relies on each platform ignoring the keywords that don't apply to it.
Frequently asked questions
Which option should I leave on the defaults?
For a public website, leave both on their defaults: Style = Sans-serif, OS = All. That produces the 10-font cross-platform chain that resolves to San Francisco on Apple, Segoe UI on Windows, and Roboto on Android/Linux. Only change the OS scope when you actually control the platform.
Why doesn't the generated CSS include my brand font?
Because the tool only emits curated system-font lists — there is no input for a custom family. Generate the stack, then prepend your brand name to the copied list, or declare the brand font with the Font Face Generator and put it first in the family list. The system stack then acts as the fallback.
Can I reorder the fonts or pick which ones to include?
No. The order and membership of each stack are fixed. The UI is two select menus and a Generate button — there are no per-font checkboxes or drag handles. If you need a different order, copy the output and edit it manually.
How do I get sans, serif, and mono variables at the same time?
Run the generator three times — once per style — and merge the :root blocks into one. Each run emits a distinctly named variable (--font-system-sans, --font-system-serif, --font-system-monospace), so they coexist without clashing. The merged-:root recipe is in the cookbook above.
Does the output apply the font anywhere besides body?
Only body. Most elements inherit from there, but form controls (input, textarea, select, button) often do not inherit font-family. Add input, textarea, select, button { font-family: inherit; } to your stylesheet so they pick up the system stack too.
Why is San Francisco not named directly?
Apple does not expose San Francisco under a usable CSS family name. The supported way to reach it is the -apple-system keyword (Safari) and BlinkMacSystemFont (Blink on macOS/iOS), both of which lead the sans-serif stacks. That's why the generated list starts with those tokens rather than a literal name.
Do I still need font-display or preload with a system stack?
No. A system stack downloads nothing, so there is no FOUT/FOIT to manage and nothing to preload. font-display and the Preload Tag Builder only matter once you add a web font. If you go hybrid (system body + brand headline), apply those to the web font only.
Is anything uploaded when I generate a stack?
No. This is a generative tool with no file input — it assembles a fixed list locally and shows the CSS in your browser. Nothing about your project is transmitted. Signed-in users get a single anonymous run counter for dashboard stats, with no content.
What does the 'Fonts in stack' metric mean?
It's the count of comma-separated entries in the emitted list, including the trailing generic family. Sans-serif / All is 10; sans-serif / Android is 4. It's a quick sanity check that a scoped stack trimmed the names you expected.
Will a Windows-scoped stack break on a Mac?
No. Every scoped stack still ends with a generic family (sans-serif/serif/monospace), so on a non-matching OS it falls through to that generic. Scoping just removes names that can't match on the chosen platform; it doesn't make the stack OS-exclusive.
Can I get the output as a design token or for iOS/Android native code?
Not from this tool — it emits CSS only. For multi-platform tokens (Style Dictionary, Swift, Kotlin) generated from the same stacks, follow Generate System Font Stacks Programmatically, which uses the runner API or the source data directly.
How do I size the body text after picking a stack?
Use a separate tool — the family and the size are independent concerns. The clamp() Font Size Generator gives a fluid font-size, and the Typography Scale Builder gives a full modular scale you can layer on top of the system stack variable.
Where should the generated CSS go in my stylesheet?
Put the :root block near the top of your global stylesheet (before component styles) so the custom property is defined before anything references it. The body rule can stay with it or move to your base/reset layer — order between the two doesn't matter as long as :root is parsed.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.