Contents

Fonts

This is this widget's live typeface catalog. Composer and Preview read these draft faces. Publish to send them with the published (and dev.) embed. A new widget starts as a snapshot of the project catalog; edits here stay on this widget.

The typeface never lived in the token catalog

A gallery widget can copy every brand token and every utility class and still render in Times. The typeface was never in those catalogs. On a Next.js host, next/font mints a name like --font-geist-sans and puts Geist on body. A class named font-sans is only a pointer at that name. The widget that lands on a page that never ran that loader does not get the face.

CSS then makes it worse. If font-family asks for var(--font-geist-sans) and that name is missing, the browser drops the entire declaration. It does not walk the comma list to sans-serif. The fallback is Times.

Tokens cannot fetch a face. Class bodies wrap as a descendant rule, so a pasted @font-face is ignored. Fonts are the third catalog: RuleCMS loads the face and, when you ask, sets font-family on the widget so Preview and the published embed match.

Where this is really useful

You wantWithout this catalogWith this catalog
A widget that matches a Tailwind / next/font gallery on a host that did not load GeistTokens point at --font-geist-sans. The name is missing. Type falls back to Times.RuleCMS registers Geist and defines the token on the widget. The embed matches Preview.
A system stack only — no network, never GeistThe widget inherits whatever the host body uses, or Times if that inheritance never arrives.Choose stack-only. The browser uses faces it already has. Nothing downloads.
A Google family with specific weights and subsetsSomeone pastes a gstatic URL that goes stale, or the face never loads on a partner page.You pick the family, weights, and subsets. RuleCMS builds the Google Fonts CSS URL. Authors never paste a file path.
A host that already runs next/font (or ships the same faces)Works today. Do not double-load the face.Turn published inclusion off. Composer and Preview still use the catalog so authors see the typeface while they work.
You do not have to choose between RuleCMS and your stylesheet. Most teams keep the same family name in both places. RuleCMS includes this widget's fonts on the published embed by default so the embed looks correct even on a page that never loaded Geist. If that double load is a problem, turn inclusion off — the radios are described below.

When to use it / when not to

Use this catalog when RuleCMS must own type on every embed host — a landing page, a partner site, or any route that did not run your font loader. Fill it on this widget. Composer and Preview show the face; publish to send it with the embed.

Skip the catalog, or turn published inclusion off, when the host stylesheet already registers the face and sets font-family on the widget's ancestors. Pointers in tokens and classes still work. RuleCMS just does not ship a second copy of the files.

This widget's catalog

Open the widget, then CSS Settings, then Fonts. This catalog belongs to this widget.

To change the template for the next widget you create, edit the project catalog.

View opens the preview (the default). Edit is /fonts/edit.

  • Preview shows the CSS this catalog will emit — a Google Fonts stylesheet link when a face is loaded, plus the token and widget font-family on the render surface. That listing is for authors. Published widgets do not dump a :root rule onto your page.
  • Edit is a card per family. Invalid fields are refused per row. Catalog Save is explicit. You need permission to manage this widget; viewing is a separate permission.

A new widget starts as a copy of the project fonts catalog. Until someone saves faces here (or the project had none to copy), this widget relies on the host typeface — the Times path if that host never registered the face.

What each field means

FieldWhat it is
FamilyThe face name — Geist, Inter. Not a CSS file. For Google, you pick from the allowlist.
CSS variable (optional)The token this face defines, for example --font-geist-sans. Utilities and class bodies may keep pointing at it. A token that says var(--font-geist-sans) without this entry is the Times bug.
FallbacksSegoe UI, system-ui, sans-serif. Comma-separated family names. RuleCMS builds the file URL; you pick the family — you never paste a network path here.
Source: stack-onlyUse faces the browser already has. No download. That matches “looks like a modern sans site.” It will never be Geist.
Source: GoogleAn allowlisted family plus weights and subsets. RuleCMS writes the Google Fonts CSS URL. You never paste a gstatic path.
Use as the widget's default typefaceAt most one row. Sets font-family on the widget's first real children. Other rows only mint a token and load the face, so a class or leaf can point at them.

Color palettes

Color fonts (COLRv1) can ship more than one set of ink. The host registers those sets with @font-palette-values, then a leaf or class points at the name with font-palette. That at-rule is document-global — a class body cannot register it, and tokens reject @. Palettes are optional fields on the same font card, not a second catalog.

Add a palette when the face needs a brand set of override colors and the embed host will not register it. Leave palettes empty when the host already owns font-palette, or when the face is not a color font — stack-only rows accept them, but they usually do nothing useful.

FieldWhat it is
Palette nameA dashed ident such as --brand. RuleCMS emits @font-palette-values --brand. At most four palettes per face.
Override colorsIndex plus a color: hex, a named color, or var(--token). No url(. At most eight overrides per palette.
Use as the widget's default paletteAt most one row in this catalog. Sets font-palette: --brand on the widget's first real children. Other palettes only register; a class can still point at them.

The Fonts inclusion radios also control palettes. When published widgets exclude fonts, RuleCMS does not emit @font-palette-values either. There is no separate palettes toggle.

@font-palette-values --brand {
  font-family: Geist;
  override-colors: 0 #111111, 1 var(--brand-accent);
}

Published widgets

On this widget's Fonts page, the Published widgets accordion holds two radios. These radios control whether this widget's published and dev. embed carry the definitions. Composer and Preview still use this widget's catalog when inclusion is off. Changing a radio saves immediately. Catalog Save is separate.

The first radio is selected by default:

  • Include these fonts when published widgets are rendered. A non-empty catalog is sent with the widget and the renderer loads the faces inside the widget — including any color palettes on those faces. This is how RuleCMS makes Preview and the live embed match.
  • My apps already load these fonts. Do not include them again when the widget is being rendered. Published widgets still point at --font-geist-sans, but RuleCMS does not ship the files. Your host must register the face. Use this when the app already runs next/font.
Composer and Preview still use this widget's catalog when inclusion is off. Authors keep seeing the typeface while they work. Only the published (and Development-draft) embed on your site stops carrying the extra faces. Empty catalog plus the setting on is the same as off: there is nothing to include.
If you turn inclusion off, the host must register every face the widgets use. A missing var() on the live page drops the whole font-family — Times again. Check the published widget on a real route after you flip the setting.

What the renderer does

Composer and Preview read this widget's draft catalog. The published embed and the Development (dev.) draft GET read the published widget's catalog. Publish is how a draft catalog goes live.

A new widget starts as a copy of the project catalog. After that, edits here do not write back to the project, and later project edits do not come back here.

SurfaceWhat happens
Composer canvas and PreviewAlways inject from this widget's draft catalog when it is non-empty. The published-inclusion flag is ignored here so authors still see the result while they work.
Published widget (React SDK and HTML custom element)The widget fetch includes this widget's catalog when inclusion is on. Nested collections inherit. You do not get a copy per collection.

When the widget catalog is applied, the HTML custom element ships the same React bundle, so the emit is the same.

Hosts already fetch the widget once. RuleCMS hangs fonts on that same response as a sibling of the widget, its collections, CSS variables, and class definitions — not folded into itemList.

{
  "success": true,
  "data": {
    "widget": { "publishedKey": "…", "itemList": "…", "…": "…" },
    "childCollections": { },
    "fontFaces": [
      {
        "family": "Geist",
        "cssVariable": "--font-geist-sans",
        "fallbacks": ["Segoe UI", "system-ui", "sans-serif"],
        "applyToWidget": true,
        "source": { "kind": "google", "weights": [400, 700], "subsets": ["latin"] },
        "palettes": [
          {
            "name": "--brand",
            "overrideColors": [
              { "index": 0, "color": "#111111" },
              { "index": 1, "color": "var(--brand-accent)" }
            ]
          }
        ]
      }
    ]
  }
}

fontFaces is omitted when the catalog is empty, when inclusion is off, or if the catalog cannot be read. When inclusion is off, the same response also sends includeFontFaces: false so renderers that still receive a list must not emit it. The widget still loads.

The React renderer (@rulecms/widget-react) and the HTML custom element turn a non-empty catalog into a Google stylesheet link when a face is loaded, plus one scoped <style> on the widget's first real children. @font-face (when emitted) is unscoped — a face has to register on the document. The token and font-family land on [data-rulecms-published-key] > *, not on :root:

[data-rulecms-published-key="env---widget-abc"] > * {
  --font-geist-sans: Geist, "Segoe UI", system-ui, sans-serif;
  font-family: var(--font-geist-sans);
}

The Development draft fetch (Development Integration) reads the same fields from the draft widget row, so a dev. token preview on your site matches composer.

Good to know

  • Light DOM does not mean the host body wins. Once RuleCMS sets font-family on the widget's children, host type no longer inherits into that tree. Turn inclusion off if you want the host face to win.
  • This catalog belongs to this widget. Copy and promote copy these columns as-is. The project catalog is only the starting snapshot.
  • Publish to restyle the live embed. Composer and Preview update when you save this draft catalog.
  • The composer font-family picker is a different tool. That drawer control is an allowlisted stack (system faces). It does not load Geist. This catalog is who defines --font-geist-sans.
  • React and the HTML embed, this pass. Other framework SDKs are not in this release.
  • Keep the list small. At most 12 fonts and 8 fallbacks per face. Google families, weights, and subsets come from an allowlist — RuleCMS builds the file URL; you pick the family.

Related docs