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 this hero so the spin blends. Register
--anglehere and the canvas interpolates. Edits stay on this widget. They do not write back to the project. - Pull later project types onto this widget. When the template moves, Sync from Project copies those registrations here. Entries that exist only on this widget stay.
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 belongs to this widget. A new widget starts as a snapshot of the project template. After that, edits here stay here. Composer and Preview emit the at-rule from these draft registrations. Publish is how they reach the published (and dev.) embed.
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.
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. Later project edits do not come back here unless you click Sync from Project.
| Surface | What happens |
|---|---|
| Composer canvas and Preview | Always 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. |
You need widget edit for this environment — the same permission as the composer — to change registrations or click Sync from Project. Viewing CSS settings is a separate, lower permission. A viewer sees the catalog and no edit button.
Type --angle so the hero interpolates
You do this on the widget management page — the same place you open the composer. If the project already typed --angle, Sync is faster than filling the row again. If only this hero should own the type, add it here.
- Open the organization → team → project → environment → Maya's homepage hero (or the Default Widget).
- Click CSS Settings. You land on Widget CSS Settings.
- On the CSS Properties card, click Edit CSS Properties (or View CSS Properties to scan first). The Sync from Project button also lives on this catalog page and on the hub.
- To pull the project template: click Sync from Project. To add a type only this widget needs: open Edit, click Add property, type
--angle, pick<angle>, set initial0deg, then Save Changes. - On Staging or Production, publish when the live embed should carry the new types. Development streams the draft — see Development Integration.
That is a safe stop. Composer already emits @property --angle when the row is saved. Publish is how visitors get the interpolation. Syncing updates the draft only — the live site does not move until she publishes again.
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.
--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.This widget's catalog
Open the widget, then CSS Settings, then CSS Properties. This catalog belongs to this widget.
To change the template for the next widget you create, edit the project catalog.
Tokens this widget already named can interpolate once you type them here.
- Preview dumps the catalog as emitted
@propertyblocks — not a:rootlisting 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.
Sync from the project
Once this widget exists, its CSS settings are its own. The project catalog does not update them automatically. A Sync from Project button lives on this CSS Properties page — preview and edit — and on the widget's CSS Settings hub.
Syncing copies the project's version of an entry onto this widget. When both sides share an id, the project's value replaces the widget's. Entries that exist only on this widget are kept.
The button reads Synced with Project and is disabled when there is nothing to pull. Tiles on the CSS Settings hub show Project updates available when the project has updates for that type.
Syncing updates the draft. Publish the widget for live embeds to change. You can sync one type from this page or all nine from the hub.
Editing the project catalog never changes a widget by itself. A person must 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.
When the button is disabled
- Unsaved edits — the helper reads Save or discard your changes before syncing. That stop exists so a later Save cannot overwrite what was just synced.
- No project catalog — the helper reads This project has no CSS settings to sync.
- Already in sync — the button itself reads Synced with Project and stays disabled.
Who can sync
Sync is governed by the widget edit permission for that environment — the same permission needed to change the widget in the composer. Viewing CSS settings is a separate, lower permission, and a viewer sees no button at all. Project CSS permission is not required: anyone who can edit the widget can pull project values onto it.
- Missing permission — users who can view CSS settings but cannot edit the widget in that environment do not see the button.
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
@propertyout 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.
Turn off inclusion on published widgets
On this widget's CSS Properties 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 property registrations 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 properties. 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
@propertyblocks. Your host stylesheet must register them. Use this to avoid two copies of the same payload: one in your stylesheet, one inside the widget.
--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.
Composer already interpolates --angle on the canvas when the type is saved. Publish is how those draft registrations reach the live embed. (Published traffic may sit in widget-cache for a short TTL — the same delay itemList already has.)
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 Variables — This page types a name the Variables page defines. Independent inclusion toggle. Put
--angle: 0degthere first. - CSS Properties — The project template for the next widget you create.
- Class Definitions — The utility bodies that often
transitionoranimatethose names. A class body cannot hold@property. - Keyframes — Named
@keyframes. Typed properties are what those frames can interpolate. - Fonts — The typeface catalog; another unscoped at-rule.
- Themes — Host-condition overrides for the same
--names. Light values stay on CSS Variables; this catalog still types the name. - Widgets — CSS Settings on the widget management page: this catalog, Sync from Project, and Generate Tailwind.
- Generate Tailwind CSS — Compile the class names this widget already uses. That flow can add
@propertyrows here; it does not replace this catalog. - Cascade layers, Position try, and Widget chrome — the rest of the project templates on the same home. Widget copies sit under Widgets in the sidebar.
- Projects, Environments & Tokens — The pipeline, and project CSS as the template for the next widget.
- Development Integration — Preview the live Development draft with a
dev.token. - HTML Custom Element Embed — The script tag that mounts a widget in light DOM so host types still reach it.
- API Reference — The 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 belongs to this widget. Copy and promote copy these columns as-is. The project catalog is only the starting snapshot. Use Sync from Project on this page or the CSS Settings hub to pull later project updates.
- Same name, last widget wins.
@propertyis document-global. Two embeds that type--angledifferently 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
@propertyfor the same--nameis how two widgets — or RuleCMS and the host — start fighting.