Contents

CSS Properties

A token can hold --angle: 0deg. Until the project catalog types that name with @property, transitions and keyframes stay discrete. Project CSS Properties let RuleCMS emit typed custom properties so composer, Preview, and published widgets can interpolate — or leave the registrations to your site.

A token can hold a value. This catalog types it

A CSS Variables entry can set --angle: 0deg. It cannot say “this is an <angle>, interpolable, initial 0deg.” Without @property, a gradient-border spin or a token color fade stays discrete or no-ops. The name is defined. The type is not.

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. Project CSS Properties are the missing catalog: a project-wide map of name → syntax, inherits, initial value. Fill it once and composer, Preview, and — by default — published widgets emit the at-rule.

This page is typed @property, not the token catalog. The Variables page still defines the name. This catalog types that same --name.

When to use it / when not to

You wantWithout typed propertiesWith this catalog
A token that interpolates under transition or @keyframes on a host that never registered @propertyThe value jumps, or the animation is a no-op, unless the host already typed the name.RuleCMS ships @property --angle with the widget. The embed can blend.
A host that already registered the same --namesWorks today. Do not double-register.Turn published inclusion off. Composer and Preview still use the catalog.
Tailwind v4 @theme is one host that already does this — not a requirement. If your site already registers those names, leave inclusion off so RuleCMS does not emit a second @property.
The full project catalog is included — not “only properties this widget uses.” Same choice as CSS variables and class definitions.

The project catalog

Open an environment, then CSS Properties. The page is project-scoped: Development, Staging, and Production share it.

  • Preview dumps the catalog as emitted @property blocks — not a :root listing and not the token catalog.
  • Edit is a row of name, syntax, inherits, and initial value. Type --angle, pick <angle>, set initial 0deg — not a pasted @property --angle { … }. Combined syntax strings such as "<color> | <image>" are not accepted; pick * if the value is untyped. Invalid rows are refused. Catalog Save is explicit.

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 40 properties; 200 characters per name and per initial value. Initial values reject url(, @, braces, and ;. Any syntax other than * requires a non-empty initial value. When syntax is * and the initial value is empty, initial-value is omitted on emit.

What the renderer does with the catalog

SurfaceWhat happens
Composer canvas and PreviewAlways injects an unscoped @property stylesheet. The published-inclusion flag is ignored here so authors still see interpolation while they work.
Published widget (React SDK and HTML custom element)The widget fetch includes the catalog when inclusion is on. The renderer emits one <style> of @property rules. The at-rule is document-global — it is not scoped to the published-key span, and the name is not prefixed. RuleCMS never styles :root or html.

You do not republish a widget to retype a name. The catalog is joined when the host fetches the widget. The next uncached fetch picks up the current rules.

Published widgets

On the project's CSS Properties page, the Published widgets accordion holds two radios. Changing a radio saves immediately. Catalog Save is separate.

  • 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 the default.
  • 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.
Composer and Preview still use the catalog when inclusion is off. Only the published (and Development-draft) embed on your site stops carrying the extra rules.
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.

How a published widget receives the catalog

Hosts already fetch the widget once. RuleCMS hangs the catalog on that same response as a sibling of the widget, its collections, CSS variables, class definitions, fonts, keyframes, and themes — not folded into itemList. The field is cssProperties (not a vague properties).

{
  "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.

The React renderer (@rulecms/widget-react) and the HTML custom element turn a non-empty catalog into one <style>, for example:

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

Good to know

  • One catalog per project. Not per environment, not per widget.
  • 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.
  • React and the HTML embed this pass. Other framework SDKs do not emit this catalog yet.
  • 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.

Related docs