What you can do
You already opened Maya's homepage hero from Widgets and started composing. The rounded corners on that hero should be rounded-xl — the same class the rest of her product already uses. A border-radius picker that only stores 0.75rem is a second copy of that system. It looks right the day she publishes and wrong the day the Tailwind pane moves.
Two everyday jobs:
- Name the utilities once for the next widget. Add
rounded-xl,px-4, andring-parity-accent/30here. A widget she creates after that starts with those names already filled in. - Point the hero at those names, not a typed radius. In the composer, CSS Classes on a column, row, or the widget itself stores
rounded-xl. The widget carries the name. The catalog — or her site — supplies what it means.
That same pattern covers a spacing utility the rest of the app already uses, a ring or shadow that nests color-mix(), and a landing page that never compiled her Tailwind pane. Authors stay in the composer. Engineers keep one vocabulary.
How a class catalog thinks
Class names already travel. A column, a row, or the widget can carry rounded-xl today — that is CSS Classes in the Modify drawer. Those names are a contract. Someone has to define them. The host stylesheet can do that — Maya's product already does, in the Tailwind pane that compiled rounded-xl. RuleCMS can do it too, in a catalog of class names and declaration blocks.
This is the sibling of CSS Variables. Tokens are --name: value. Class definitions are .name { declaration-list }. They are independent catalogs: a host that already ships Tailwind can turn class inclusion off and still include CSS variables, or the reverse.
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 names 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 class catalog itself.
Here is the case that surprises people. Maya edits rounded-xl 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.
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.
| Surface | What happens |
|---|---|
| Composer canvas and Preview | Do 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 class and flip the inclusion radios. Those radios set the default copied onto new widgets. They do not change widgets that already exist.
Put rounded-xl on the hero
You do this on environment home, then in the composer. The Default Widget in Production already exists — it will not pick up a new class until someone syncs. A widget she creates after this save starts with the class already there.
- Open the organization → team → project → environment you want to work in.
- Under CSS catalog, click Class Definitions. You land on this project page — Development, Staging, and Production share it.
- Open Edit. Click Add class. Name:
rounded-xl. Declarations: the radius, for exampleborder-radius: var(--radius-xl);. Save Changes. - 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 class.
- In the composer, click the hero column. Open CSS Classes and type
rounded-xl, exactly as you would in HTML.
That is a safe stop. The canvas shows the radius when the widget's catalog has the class. Come back here when the next widget should start from a richer utility set, or open that widget's catalog when only the hero should change.
Putting a class name on a column
Columns, rows, and the widget itself each have a CSS Classes accordion in the Modify drawer. Type the names exactly as they should appear in HTML, space-separated. The control-by-control guide is CSS Classes.
The project catalog
Open an environment, then Class Definitions. 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 classes.
- Preview shows the catalog as readable
.name { … }blocks — the same shape as a Tailwind utilities pane, not:root. That listing is for authors. It is not how published widgets inject classes on your site (an unscoped rule would leak into the rest of your layout). - Edit is a name-and-declarations form. Add class starts a row. Invalid names and bodies are refused per row. Save Changes stays disabled until the catalog is clean.
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.
var() and @supports.A project has no catalog row until someone saves classes for the first time. Until then, widgets that carry class 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 defines them too — and that is fine
The original class-name model is still the right one for a product that already has a Tailwind pane: the host stylesheet defines rounded-xl, and RuleCMS widgets inherit it. The HTML custom element mounts in light DOM for exactly that reason — your utilities 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 declaration blocks on the published widget. Inside that widget, the RuleCMS rules win over inherited host utilities, 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 Tailwind 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 utility.
- You are still rolling utilities out on the host and the catalog is ahead of the compiled pane.
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 the project's Class Definitions 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 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 how RuleCMS makes Preview and the live embed match.
- 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. Use this to avoid two copies of the same payload: one in your Tailwind pane, 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 declaration blocks 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 renderer needs the classes 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": { "--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. You never lose a page because a class 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, for example:
[data-rulecms-published-key="env---widget-abc"] .rounded-xl {
border-radius: var(--radius-xl);
}Nested collections inherit. Other widgets on the same page keep their own catalogs. You do not get a copy per collection.
The Development draft fetch (Development Integration) reads the same fields from the draft widget row, so a dev. class preview on your site matches composer.
After a widget is created, restyling a class 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 classes still work. Widgets that never used CSS Classes are unchanged.
What to read next
This page is the story and the first walkthrough. The Modify-drawer accordion, the other catalogs, and the request your app already sends live on the pages below.
- CSS Classes — Putting names on columns, rows, and widgets. This catalog is what those names mean.
- CSS Variables — The token catalog. Independent inclusion toggle. A class body can call
var(--radius-xl); the token still lives there. - widget Class Definitions — This widget's live catalog, Sync from Project, and the inclusion radios that actually ship with the embed. That page sits under Widgets CSS Settings.
- Widgets — CSS Settings on the widget management page: this catalog, Sync from Project, and Generate Tailwind.
- Cascade layers — Wrap these class rules in
@layer utilitiesor@layer componentsso they compete with host Tailwind instead of steamrolling it. A class body cannot contain a top-level@layer. - Generate Tailwind CSS — Compile the class names this widget already uses. That flow writes declaration blocks here; it does not replace this catalog. You still paste by hand when you already have the CSS.
- Fonts — The typeface catalog. A class body cannot register a face.
- Themes — Host-condition overrides for the same
--names. A class can nest@media; a token cannot. - CSS Properties — Typed
@propertyso tokens those classes transition can interpolate. - Keyframes, 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 utilities still reach it.
- API Reference — The public widget GET, including
classDefinitions.
How this fits
A widget is the content. A class catalog says what the class names on that widget mean. It does not replace the widget, and it does not replace Maya's Tailwind pane. Publishing is how a draft catalog reaches a Staging or Production token. The Composer is where a column 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.
- This is not “compile Tailwind inside RuleCMS.” You paste declaration blocks you already have — or you run Generate Tailwind CSS on this widget to compile the names it already uses. That generator writes into this catalog. It is not this page.
- Attaching a class name is still a different control. See CSS Classes. This catalog is what those names mean.
- Leave the catalog empty if the host owns every class. Names on elements still work. Nothing extra is sent to the published page.
- Ask whoever owns the stylesheet which names are safe. A utility 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.