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:
- Restyle this hero without teaching a hex. Change
--brand-surfacehere and the canvas follows. Edits stay on this widget. They do not write back to the project. - Pull later project updates onto this widget. When the template moves, Sync from Project copies those names here. Entries that exist only on this widget stay.
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 belongs to this widget. A new widget starts as a snapshot of the project template. After that, edits here stay here. Composer and Preview read these draft values. Publish is how they reach the published (and dev.) embed.
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.
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 names or click Sync from Project. Viewing CSS settings is a separate, lower permission. A viewer sees the catalog and no edit button.
Put --brand-surface on the hero
You do this on the widget management page — the same place you open the composer. If the project already has --brand-surface, Sync is faster than typing it again. If only this hero should own the name, 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 Variables card, click Edit Variables (or View CSS Variables 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 name only this widget needs: open Edit, click Add variable, type
--brand-surfaceand its value, then Save Changes. - In the composer, click the hero column. Switch Value / CSS variable to CSS variable and type
--brand-surface. - On Staging or Production, publish when the live embed should carry the new values. Development streams the draft — see Development Integration.
That is a safe stop. The canvas already shows the colour. Publish is how visitors see it. Syncing updates the draft only — the live site does not move until she publishes again.
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.
This widget's catalog
Open the widget, then CSS Settings, then CSS Variables. This catalog belongs to this widget.
To change the template for the next widget you create, edit the project catalog.
Style settings on this widget can point at the names you define here.
- 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:rootrule 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 — this widget simply has no RuleCMS-owned tokens yet.
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 Variables 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 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.
Turn off inclusion on published widgets
On this widget's CSS Variables 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 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.
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.
A column that points at --brand-surface paints that colour on the canvas while you edit. Publish is how those draft values 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 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.
- Using a CSS variable instead of a typed value — The Modify-drawer toggle, name rules, and Border / Box Shadow whole-property tokens.
- CSS Variables — The project template for the next widget you create.
- Class Definitions — Class-name → declaration-block catalog. A CSS class on a column still needs a stylesheet; variables you put here always preview. See also CSS Classes.
- Fonts — The typeface catalog. A token that points at
--font-geist-sansis not a face. - Themes — Host-condition overrides for the same
--names. Light values stay here. - CSS Properties — This page defines the name; that catalog types it with
@property. - Keyframes, Position try, Cascade layers, and Widget chrome — the rest of the project templates on the same home. Widget copies sit under Widgets in the sidebar.
- Generate Tailwind CSS — Compile the class names this widget already uses. That flow can add tokens here; it does not replace this catalog.
- 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 tokens still reach it.
- API Reference — The public widget GET, including
cssVariables. - MDN: Using CSS custom properties
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 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.
- 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.