Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / UI

web fonts — shipping a typeface with your app

osy font add <file.woff2> --family "<CSS family>" [--weight 400] [--style normal]

Naming a font in your theme asks for it; `osy font add` ships it. The command pins a font file in your project's lock, every compile carries it to the server, and the app's stylesheet declares the matching `@font-face` — so the typeface renders on a machine that has never seen it, instead of falling through to the next name in your stack.

stable2 examples compiled by CIuithemefontscli

Summary#

A theme names your app's type:

theme Doc { Fonts { Body = "Hedvig Letters Serif, ui-serif, Georgia, serif"; } }

That is a request, not a delivery. font-family is a preference list, and a browser walks it until it finds something it already has — so on any machine without Hedvig Letters Serif installed, the name is skipped silently and your app renders in ui-serif. Nothing errors. The page just isn't the design.

osy font add is the other half. It registers a font FILE with your app: the file is pinned by content hash in osyrin.lock, carried to the server by every compile, served back immutably, and declared as an @font-face in the app's stylesheet. The theme still names the family exactly as before — the two stay separate on purpose, because one family often needs several files (a regular, a bold, an italic) and the theme should name it once.

Signature#

osy font add <file>  --family "<CSS family name>"  [--weight <400|700|…>]  [--style <normal|italic>]
osy font list

Description#

Registering a font#

Put the file somewhere in your project and register it:

$ osy font add model/fonts/HedvigLettersSerif-Regular.woff2 --family "Hedvig Letters Serif"
✓ Registered Hedvig Letters Serif (0ab846d39150…) → model/fonts/HedvigLettersSerif-Regular.woff2
  Name it in your theme to use it, e.g.
    Fonts { Body = "Hedvig Letters Serif, ui-serif, Georgia, serif"; }
  It ships on the next compile. You are responsible for its licence.

--family is required and never guessed. A filename is not a family name — HedvigLettersSerif-Regular.woff2 provides the family Hedvig Letters Serif — and a guess that is subtly wrong produces the exact failure this feature exists to remove: the font loads, nothing matches it, and the page renders in the fallback while looking healthy.

Accepted formats are .woff2, .woff, .ttf and .otf. Prefer woff2: it is the smallest by a wide margin and every current browser reads it.

osy font list shows what your app ships:

$ osy font list
╭──────────────────────┬────────┬────────┬─────────────────────────────────────╮
│ Family               │ Weight │ Style  │ File                                │
├──────────────────────┼────────┼────────┼─────────────────────────────────────┤
│ Hedvig Letters Serif │ 400    │ normal │ model/fonts/HedvigLettersSerif-Reg… │
╰──────────────────────┴────────┴────────┴─────────────────────────────────────╯

Naming it in your theme#

Registering a file does not decide where it is used — your theme does, unchanged:

theme Doc {
  Fonts {
    Body    = "Hedvig Letters Serif, ui-serif, Georgia, serif";
    Heading = "Hedvig Letters Sans, ui-sans-serif, system-ui, sans-serif";
  }
}

Keep the fallbacks. They are what the reader sees during the moment before the file arrives, and on the rare browser that cannot use it at all.

More than one file per family#

A family is a set of faces, and each file provides one. Register each with the weight and style it covers:

$ osy font add fonts/Inter-Regular.woff2    --family "Inter"
$ osy font add fonts/Inter-Bold.woff2       --family "Inter" --weight 700
$ osy font add fonts/Inter-Italic.woff2     --family "Inter" --style italic

--weight defaults to 400 and --style to normal, so the first line above needs neither. Registrations are keyed by (family, weight, style), which is why the second and third lines ADD faces rather than replace the first. A variable font that covers a range declares it as one: --weight "100 900".

Your theme still names Inter once. The browser picks the right file per weight and style.

What ships, and when#

Every compile carries every registered font whose file has changed or that the server does not yet have, and re-verifies each file against its pinned hash first. A font edited on disk but never re-added aborts the compile rather than shipping bytes the lock does not describe:

$ osy compile
✗ font 'Inter': 'fonts/Inter-Regular.woff2' has changed since it was registered (osyrin.lock pins
  9f86d0818200…, file is 2c624232945…) — re-run `osy font add fonts/Inter-Regular.woff2 --family "Inter"` to re-pin it.

Files are stored by content address, so identical bytes are stored once no matter how many apps or versions reference them, and a recompile that changes nothing ships nothing.

The app's stylesheet then declares each face ahead of the rules that use it:

@font-face {
  font-family: "Hedvig Letters Serif";
  src: url("/_osy/font/0ab846d39150…") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

font-display: swap is deliberate: text is painted immediately in the fallback and re-painted when the file lands. The alternative is a first paint with nothing where the words should be.

Removing a font from the app#

Delete its entry from osyrin.lock and recompile. The next compile reconciles the server to what your app now declares — the row, the stored bytes and the @font-face all go together, so a stylesheet can never keep naming a face you stopped shipping.

Am I allowed to self-host this font? — licensing#

osy font add ships a file you chose, and the platform makes no licence check. Web-font licences vary in ways tooling cannot infer — some permit self-hosting freely, some by domain, some not at all. Confirm you may self-host before you register a font.

Examples#

The whole loop, from a downloaded file to a page that renders in it:

$ osy font add model/fonts/HedvigLettersSerif-Regular.woff2 --family "Hedvig Letters Serif"
✓ Registered Hedvig Letters Serif (0ab846d39150…) → model/fonts/HedvigLettersSerif-Regular.woff2

$ osy compile
✓ Osyrin compiled and applied to MarkdownDemo (6 file(s)).

with the theme naming it:

theme Doc {
  Fonts { Body = "Hedvig Letters Serif, ui-serif, Georgia, serif"; }
}

To confirm a font is really being used rather than silently falling back, measure rendered text width against a family you know does not exist. document.fonts.check() is not a test — it resolves through the fallback stack and answers true either way.

See also#

Related

theme tokens

A `theme` block names your app's design tokens — colors, spacing, radii, and more — as reusable values. A token can…

control — foreign UI controls (charts, grids, maps)

A `control` block declares the contract of a foreign UI widget — a chart, a data grid, a map — that a small JavaScript…

app.osy

The manifest at the root of every project. It names the app, says which files are model, seed, migrations and tests…