Class names already travel. This catalog says what they mean
Widgets can already carry CSS class names on columns, rows, and the widget itself. Those names are a contract with the host stylesheet: rounded-xl, px-4, ring-parity-accent/30. RuleCMS does not invent those utilities. Until now it also did not define them.
Class definitions are the missing catalog: a project-wide map of class name → CSS declaration block. Fill it once and the composer, Preview, and — by default — published widgets apply those rules, even when the host never compiled that Tailwind pane.
This is the sibling of CSS Variables. Tokens are --name: value. Class definitions are .name { declaration-list }. They are independent toggles: a host that already ships Tailwind can turn class inclusion off and still include CSS variables, or the reverse.
Where this is really useful
| You want | Class names only | Class names plus this catalog |
|---|---|---|
| A widget that looks like a host Tailwind gallery even on a page that did not compile those utilities | Classes are present. Nothing paints unless the host CSS already defines them. | RuleCMS ships the declaration blocks with the widget. The embed matches Preview. |
Multi-line Tailwind bodies — @supports, color-mix(), shadow/ring custom properties | Impossible to reconstruct from the class name alone. | Paste the declaration list. Nested at-rules and var() are allowed. |
| A host that already has the same utilities | Works today. Do not double-define. | Turn published inclusion off. Composer and Preview still use the catalog. |
var() and @supports.The project catalog
Open an environment, then Class Definitions. The page is project-scoped: Development, Staging, and Production share it.
- Preview dumps the catalog as
.name { … }blocks — the same shape as a Tailwind utilities pane, not:root. - Edit is a name plus a textarea for the declaration body. Invalid names and bodies are refused per row. Catalog Save is explicit.
Each entry is one class:
.rounded-xl {
border-radius: var(--radius-xl);
}
.ring-parity-accent\/30 {
--tw-ring-color: color-mix(in srgb, #6d28d9 30%, transparent);
@supports (color: color-mix(in lab, red, red)) {
--tw-ring-color: color-mix(in oklab, var(--color-parity-accent) 30%, transparent);
}
}Names are stored without a leading .. Paste from DevTools with a dot and RuleCMS strips one. Tailwind extras are allowed: /, :, [, ], %, ., _, -. On emit,/ is CSS-escaped so the selector is .ring-parity-accent\/30.
Values are a CSS declaration list, not a single token value. Semicolons, braces, @supports, calc(), var(), and color-mix() are allowed. A value must stay brace-balanced so it cannot close the wrapping rule and inject a new selector. @import, <script, and similar breakouts are rejected. At most 500 classes; 4000 characters per body.
What the renderer does with the catalog
| Surface | What happens |
|---|---|
| Composer canvas and Preview | Always injects a stylesheet of .rulecms-render-surface .className { … }. The published-inclusion flag is ignored here so authors still see the utilities 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 scoped <style> of descendant class rules under that widget's published-key span. Nested collections inherit. You do not get a copy per collection. |
You do not republish a widget to restyle a class. The catalog is joined when the host fetches the widget. The next uncached fetch picks up the current bodies. (Published traffic may sit in widget-cache for a short TTL — the same delay itemList already has.)
Turn off inclusion on published widgets
On the project's Class Definitions page, the Published widgets accordion holds two radios. Changing a radio saves immediately. Catalog Save is separate.
- Include these class definitions when published widgets are rendered. A non-empty catalog is sent with the widget and the renderer defines the classes inside the widget. This is the default.
- My apps already have these CSS classes. Do not include them again when the widget is being rendered. Published widgets still carry the class names on elements, but RuleCMS does not ship the declaration blocks. Your host stylesheet must define them.
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, and CSS variables — not folded into itemList.
{
"success": true,
"data": {
"widget": { "publishedKey": "…", "itemList": "…", "…": "…" },
"childCollections": { },
"cssVariables": { "--radius-xl": "0.75rem" },
"classDefinitions": {
"rounded-xl": "border-radius: var(--radius-xl);"
}
}
}classDefinitions 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 includeClassDefinitions: false so renderers that still receive a map 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> scoped to that widget's published key, for example:
[data-rulecms-published-key="env---widget-abc"] .rounded-xl {
border-radius: var(--radius-xl);
}Good to know
- This is not “compile Tailwind inside RuleCMS.” You paste declaration blocks you already have. RuleCMS stores and emits them.
- Attaching a class name is still a different control. See CSS Classes. This catalog is what those names mean.
- One catalog per project. Not per environment, not per widget.
- Leave the catalog empty if the host owns every class. Names on elements still work. Nothing extra is sent to the published page.
Related docs
- CSS Variables — the token catalog; independent inclusion toggle
- CSS Classes — putting names on columns, rows, and widgets
- HTML Custom Element Embed
- Development Integration
- API Reference — the public widget GET, including
classDefinitions