Contents

CSS Properties

Maya already has a homepage hero. CSS Variables already named --angle: 0deg on that hero. This catalog types that name so a gradient-border spin or a color fade can interpolate — instead of jumping. This page is the project catalog: the template of typed @property rules 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 named --angle on CSS Variables --angle: 0deg. That catalog defines the name. It cannot say “this is an <angle>, interpolable, initial 0deg.” Without that type, a gradient-border spin or a token color fade stays discrete or no-ops. The name is there. The type is not.

Two everyday jobs:

  • Type the names once for the next widget. Add --angle here as <angle>, initial 0deg. A widget she creates after that starts with that registration already filled in, so a transition or keyframe can interpolate instead of jump.
  • Leave inclusion off when the host already typed those names. Tailwind v4 @theme is one host that already registers them. The hero still points at --angle. RuleCMS does not emit a second @property.

That same pattern covers typing --brand-surface as <color> so a fade can blend, or leaving a value untyped with * when interpolation is not the job. Authors stay in the catalog form. Engineers keep one vocabulary.

How typing a token thinks

A setting stores the name. CSS Variables stores what it means. Someone still has to type it if the browser should interpolate. The host stylesheet can do that — Maya's product already does, in Tailwind v4 @theme or a handwritten @property. RuleCMS can do it too, in a catalog of name, syntax, inherits, and initial value.

A class cannot hold the at-rule. Class bodies wrap as [published-key] .name { … }. @property inside that rule is ignored. A token cannot hold it either — @ and braces are rejected. That is why this catalog exists: a map of name → syntax, inherits, initial value. The Variables page still defines the name. This catalog types that same --name.

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 registrations 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 typed catalog itself.

Here is the case that surprises people. Maya types --angle on the project and expects the Production Default Widget — the hero she already built — to start interpolating. 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.

A second surprise: @property is document-global. The name is not prefixed, and the rule is not scoped to the published-key span. Two embeds that type --angle differently race — same name, last widget wins. Use the same registration, or turn inclusion off on the host that already owns the name. RuleCMS does not rename properties per published key.

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 registration and flip the inclusion radios. Those radios set the default copied onto new widgets. They do not change widgets that already exist.

The full catalog is copied onto a new widget — not “only properties this widget uses.” Same choice as CSS variables and class definitions.

Type --angle so the hero interpolates

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

  1. Open the organization → team → project → environment you want to work in.
  2. If --angle is not in the token catalog yet, add it on CSS Variables first. This page only types a name that catalog defines.
  3. Under CSS catalog, click CSS Properties. You land on this project page — Development, Staging, and Production share it.
  4. Open Edit. Click Add property. Name: --angle. Syntax: <angle>. Initial value: 0deg. Leave Inherits unchecked for a spin that should stay on this element. Save Changes.
  5. 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 type.

That is a safe stop. The next widget starts with --angle typed so a transition or keyframe can interpolate. Come back here when the next widget should start from a richer set of types, or open that widget's catalog when only the hero should change.

Name, syntax, inherits, initial value

Edit is a row of four fields — not a pasted @property --angle { … }. Type the name the same way CSS Variables does, starting with two hyphens. Pick a syntax from the allowlist. Combined strings such as "<color> | <image>" are not accepted; pick * if the value is untyped. Check Inherits when children should see the same typed value. Any syntax other than * needs a non-empty initial value.

The Variables page still owns the value. This catalog types --angle so the browser can interpolate it. The colour or degree the hero actually uses still lives on CSS Variables — or on Maya's host stylesheet.

The project catalog

Open an environment, then CSS Properties. 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 types.

  • Preview dumps the catalog as emitted @property blocks — not a :root listing and not the token catalog. That listing is for authors. It is not how published widgets inject types on your site (the at-rule is document-global either way).
  • Edit is the name / syntax / inherits / initial value form. Add property starts a row. Invalid rows are refused. Save Changes stays disabled until the catalog is clean.

Each entry is one typed custom property:

@property --angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

Names must start with -- (same rule as CSS Variables). At most 200 properties; 200 characters per name and per initial value. Initial values reject url(, @, braces, and ;. When syntax is * and the initial value is empty, initial-value is omitted on emit.

A project has no catalog row until someone saves properties for the first time. Until then, widgets that animate those 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 registers them too — and that is fine

The original type model is still the right one for a product that already registers custom properties: the host stylesheet types --angle, and RuleCMS widgets inherit it. Tailwind v4 @theme is one host that already does this — not a requirement.

Teams that also fill the RuleCMS catalog usually register the same names. By default RuleCMS still includes those @property blocks on the published widget. The rest of your page is untouched: RuleCMS never styles :root or html. The at-rule itself is still document-global, so two widgets that disagree will race.

That default is the useful one when:

  • The widget is on a page that never registered those names (a landing page, a partner site, an email-adjacent HTML shell).
  • Editors need the published embed to interpolate the way Preview does, even if the host forgot a type.
  • You are still rolling @property out on the host and the catalog is ahead of the stylesheet.

It is the wrong default when the host stylesheet is the only registration 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 interpolate even on a page that has not registered those types yet. If that double registration is a problem, turn inclusion off.

Turn off inclusion on published widgets

On the project's CSS Properties 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 typed CSS properties when published widgets are rendered. A non-empty catalog is sent with the widget and the renderer registers the types. This is how RuleCMS makes Preview and the live embed interpolate the same way.
  • My apps already register these @property rules. Do not include them again when the widget is being rendered. Published widgets still carry token names that need those types, but RuleCMS does not ship the @property blocks. Your host stylesheet must register them. 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 register every --name the widgets animate. A missing @property on the live page leaves the token discrete. 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 registrations on that same response as a sibling of the widget, its collections, and CSS variables — not folded into itemList, not copied onto each collection. The field is cssProperties (not a vague properties) so the renderer can emit @property without guessing which list is types.

{
  "success": true,
  "data": {
    "widget": { "publishedKey": "…", "itemList": "…", "…": "…" },
    "childCollections": { },
    "cssProperties": [
      {
        "name": "--angle",
        "syntax": "<angle>",
        "inherits": false,
        "initialValue": "0deg"
      }
    ]
  }
}

cssProperties 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 includeCssProperties: false so renderers that still receive a list must not emit it. Missing on the payload means include (legacy clients). The widget still loads. You never lose a page because a type 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> of unscoped @property rules, for example:

@property --angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

Nested collections inherit. Other widgets on the same page keep their own catalogs — until two of them register the same name. Same name, last widget wins.

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

After a widget is created, typing 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 @property rules still work. Widgets that never animated a token are unchanged.

React and the HTML embed this pass. Other framework SDKs do not emit this catalog yet.

What to read next

This page is the story and the first walkthrough. The token catalog this page types, the other catalogs, and the request your app already sends live on the pages below.

  • CSS VariablesThis page types a name the Variables page defines. Independent inclusion toggle. Put --angle: 0deg there first.
  • widget CSS PropertiesThis widget's live catalog, Sync from Project, and the inclusion radios that actually ship with the embed. That page sits under Widgets CSS Settings.
  • Class DefinitionsThe utility bodies that often transition or animate those names. A class body cannot hold @property.
  • KeyframesNamed @keyframes. Typed properties are what those frames can interpolate.
  • FontsThe typeface catalog; another unscoped at-rule.
  • ThemesHost-condition overrides for the same --names. Light values stay on CSS Variables; this catalog still types the name.
  • WidgetsCSS Settings on the widget management page: this catalog, Sync from Project, and Generate Tailwind.
  • Generate Tailwind CSSCompile the class names this widget already uses. That flow can add @property rows here; it does not replace this catalog.
  • Cascade layers, Position try, and Widget chromethe rest of the project templates on the same home. Widget copies sit under Widgets in the sidebar.
  • Projects, Environments & TokensThe pipeline, and project CSS as the template for the next widget.
  • Development IntegrationPreview the live Development draft with a dev. token.
  • HTML Custom Element EmbedThe script tag that mounts a widget in light DOM so host types still reach it.
  • API ReferenceThe public widget GET, including cssProperties.
  • MDN: @property

How this fits

A widget is the content. CSS Variables names the colours and spacing that widget points at. This catalog types those names so a transition or keyframe can interpolate. It does not replace the widget, and it does not replace Maya's stylesheet. 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.

  • Same name, last widget wins. @property is document-global. Two embeds that type --angle differently will race. Use the same registration, or turn inclusion off on the host that already owns the name. RuleCMS does not rename properties per published key.
  • Leave the catalog empty if the host already registered the same names. Token names on elements still work. Nothing extra is sent to the published page.
  • Ask whoever owns the stylesheet which names are already typed. A second @property for the same --name is how two widgets — or RuleCMS and the host — start fighting.