Contents

CSS Variables

Maya already has a homepage hero. The brand colour on that hero should be --brand-surface — the same name the rest of her site uses — not a hex she typed into a picker. This page is the project catalog: the template copied onto each new widget. After that the widget owns the copy. Editing here does not restyle widgets that already exist.

What you can do

You already opened Maya's homepage hero from Widgets and started composing. The brand colour on that hero should be --brand-surface — the same name the rest of her product already uses. A picker that only stores #f7f7f5 is a second copy of that system. It looks right the day she publishes and wrong the day the brand moves.

Two everyday jobs:

  • Name the palette once for the next widget. Add --brand-surface, --brand-space-md, and --border-hairline here. A widget she creates after that starts with those names already filled in.
  • Point the hero at those names, not a hex. In the composer, a background or padding setting can store --brand-surface instead of a typed value. The widget carries the name. The catalog — or her site — supplies what it means.

That same pattern covers a spacing scale the rest of the app already uses, a whole border or shadow from the design system, and light / dark themes where the host swaps what the names mean. Authors stay in the composer. Engineers keep one vocabulary.

How a token catalog thinks

A setting stores the name. Someone has to define it. The host stylesheet can do that — Maya's product already does, on :root or a theme class. RuleCMS can do it too, in a catalog of names and values.

This catalog is the template for the next widget, not a live link to widgets that already exist. When Maya creates a widget, RuleCMS copies these names onto it — including the inclusion radios. After that the widget owns the copy. Composer, Preview, and the published embed read that widget, not this page. The Projects guide already draws that line. This page is the token catalog itself.

Here is the case that surprises people. Maya edits --brand-surface on the project and expects the Production Default Widget — the hero she already built — to move. It does not. Existing widgets stay on the snapshot they received at create until someone opens that widget's CSS Settings and clicks Sync from Project.

The renderer does not read this catalog. Composer, Preview, and the published widget GET read the widget's own catalog. This project catalog is copied onto a widget only when that widget is created.

A new widget receives a snapshot of this catalog, including the inclusion flags. Widgets that already exist are not updated automatically. Existing widgets can pull those updates later from the widget's own CSS Settings page. Copy, promote, and restore copy the source widget's catalog, not this one.

SurfaceWhat happens
Composer canvas and PreviewDo not read this catalog. They read the widget's draft columns.
Published widget (React SDK and HTML custom element)Reads that widget's published columns. Inclusion on that widget decides whether the embed carries them.

You need a Project CSS Catalog group at View, Manage, or Admin. View can open this page. Manage is enough to add a name and flip the inclusion radios. Those radios set the default copied onto new widgets. They do not change widgets that already exist.

Put --brand-surface on the hero

You do this on environment home, then in the composer. The Default Widget in Production already exists — it will not pick up a new name until someone syncs. A widget she creates after this save starts with the name already there.

  1. Open the organization → team → project → environment you want to work in.
  2. Under CSS catalog, click CSS Variables. You land on this project page — Development, Staging, and Production share it.
  3. Open Edit. Click Add variable. Name: --brand-surface. Value: the brand colour, for example #f7f7f5. Save Changes.
  4. Create a new widget from the orange Widgets card — that widget receives a snapshot of this catalog. Or open a widget that already exists, go to CSS Settings, and click Sync from Project so the hero picks up the name.
  5. In the composer, click the hero column. Above Background Color (or padding, or border), switch the Value / CSS variable toggle to CSS variable. Type --brand-surface. The help text shows var(--brand-surface).

That is a safe stop. The canvas shows the colour when the widget's catalog has the name. Come back here when the next widget should start from a richer palette, or open that widget's catalog when only the hero should change.

Pointing a setting at a name

Almost every size, colour, or style control in the Modify drawer has that Value / CSS variable toggle. Type the name exactly as it should appear in CSS, starting with two hyphens. Border and Box Shadow can take a name for the whole property — --border-hairline — or for individual parts. The control-by-control guide is Using a CSS variable instead of a typed value.

The name is the contract. RuleCMS checks the shape — two hyphens, then letters, numbers, hyphens, and underscores, up to 200 characters. It does not invent a fallback if the name is missing from both the catalog and the host page. A typo looks like an unset property: nothing paints, with no error banner.

The project catalog

Open an environment, then CSS Variables. That page is the catalog for the project, not for one environment: Development, Staging, and Production share it.

To change one widget, open its CSS Settings — that guide is the override for this type.

Every new widget in the project can start from the same names.

  • Preview shows the catalog as a readable :root { … } listing so you can scan it as CSS. That listing is for authors. It is not how published widgets inject tokens on your site (a :root rule on the host page would leak into the rest of your layout).
  • Edit is a name-and-value form. Add variable starts a row. Invalid names and values are refused per row. Save Changes stays disabled until the catalog is clean.

Each entry is one custom property:

--brand-surface: #f7f7f5;
--brand-space-md: 16px;
--border-hairline: 1px solid #ddd;

Values are a single CSS value, not a stylesheet. Nested var(--other) is allowed, so a token can be built from other tokens. Semicolons, braces, comments, and url( are not. At most 200 names. An empty catalog is a valid state — the project simply has no RuleCMS-owned tokens yet.

A project has no catalog row until someone saves variables for the first time. Until then, widgets that point at names rely entirely on the host stylesheet, the same as they always have.

Updates for existing widgets

Editing the project catalog never changes a widget by itself. A person must open the widget and click Sync from Project.

Widgets are per-environment (Development, Staging, Production), but the project catalog is shared across all three. Syncing a Production widget pulls from the same project values a Development widget would.

There is no project-side action that pushes settings to many widgets. Syncing is always started from a widget — one type from its feature page, or all nine from the hub.

Your site probably defines them too — and that is fine

The original token model is still the right one for a product that already has a design system: :root or a theme class on the host page defines --brand-surface, and RuleCMS widgets inherit it. The HTML custom element mounts in light DOM for exactly that reason — your variables and fonts still reach the widget tree.

Teams that also fill the RuleCMS catalog usually paste the same names. By default RuleCMS still includes those definitions on the published widget. Inside that widget, the RuleCMS values win over inherited host tokens, because they are specified closer to the content. The rest of your page is untouched: injection is scoped to the widget, not dumped on :root.

That default is the useful one when:

  • The widget is on a page that does not load your design-system stylesheet (a landing page, a partner site, an email-adjacent HTML shell).
  • Editors need the published embed to match Preview even if the host forgot a token.
  • You are still rolling tokens out on the host and the catalog is ahead of the stylesheet.

It is the wrong default when the host stylesheet is the only source of truth you want at runtime — especially if RuleCMS and the host might disagree for a while.

You do not have to choose between RuleCMS and your stylesheet. Most teams keep the same names in both places. RuleCMS copies this catalog onto each new widget by default, including the inclusion flag, so a new embed can look correct even on a page that has not loaded your tokens yet. If that double definition is a problem, turn inclusion off.

Turn off inclusion on published widgets

On the project's CSS Variables page, the Published widgets accordion holds two radios. These radios set the default copied onto new widgets. Changing them does not change widgets that already exist. Changing a radio saves immediately. Catalog Save is separate.

The first radio is selected by default:

  • Include these CSS variables when published widgets are rendered. A non-empty catalog is sent with the widget and the renderer defines the names inside the widget. This is how RuleCMS makes Preview and the live embed match.
  • My apps already have the CSS variables. Do not include them again when the widget is being rendered. Published widgets still point at --brand-surface, but RuleCMS does not ship the definitions. Your client project must define the names. Use this to avoid two copies of the same payload: one in your stylesheet, one inside the widget.
Composer and Preview still use the widget catalog when inclusion is off. These radios only set the default copied onto a new widget. They do not change widgets that already exist. Empty catalog plus the setting on is the same as off: there is nothing to include.
If you turn inclusion off, the host must define every name the widgets use. A missing token on the live page paints as nothing — the same as a typo. Check the published widget on a real route after you flip the setting.

How a published widget receives the catalog

Hosts already fetch the widget once. When inclusion is on and the catalog is not empty, RuleCMS hangs those names on that same response as a sibling of the widget and its collections — not folded into itemList, not copied onto each collection. The renderer needs the names next to the layout so it can define them inside the widget without touching your html element.

{
  "success": true,
  "data": {
    "widget": { "publishedKey": "…", "itemList": "…", "…": "…" },
    "childCollections": { },
    "cssVariables": {
      "--brand-surface": "#f7f7f5",
      "--brand-space-md": "16px"
    }
  }
}

cssVariables 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 includeCssVariables: false so renderers that still receive a catalog must not emit it. The widget still loads. You never lose a page because a token row is missing.

The React renderer (@rulecms/widget-react) and the HTML custom element (which bundles that renderer) turn a non-empty catalog into one <style> scoped to that widget's published key. Nested collections inherit. Other widgets on the same page keep their own catalogs.

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.

After a widget is created, restyling a token means editing that widget's catalog (or the host stylesheet). Editing this page does not change widgets that already exist.

If the catalog is empty, nothing extra is emitted. Host tokens still work. Widgets that never used CSS variables are unchanged.

What to read next

This page is the story and the first walkthrough. The Modify-drawer toggle, the other catalogs, and the request your app already sends live on the pages below.

How this fits

A widget is the content. A token catalog names the colours and spacing that widget points at. It does not replace the widget, and it does not replace Maya's stylesheet. Publishing is how a draft catalog reaches a Staging or Production token. The Composer is where a setting stores the name.

This catalog is the template for new widgets. It is shared across Development, Staging, and Production. The renderer reads the widget, not this page. Existing widgets are not updated automatically when you edit here; they can pull the new values from CSS Settings.

  • This is not a theming engine. No media queries, no [data-theme] selectors, no dark-mode switch in RuleCMS. Those belong in your stylesheet — or on Themes as host-condition overrides. The catalog is a flat list of names and values.
  • Leave the catalog empty if the host owns every name. Pointers in the composer still work. Nothing extra is sent to the published page.
  • Ask whoever owns the stylesheet which names are safe. A variable defined only inside one app component will not resolve for a widget placed somewhere else — unless that name is also in the widget catalog and that widget's inclusion is on.