Contents

Class Definitions

This is the class catalog on Maya's homepage hero — this widget, not the project. Composer and Preview read these draft declaration blocks, so rounded-xl paints on the canvas while she works. Publish to send them with the published (and dev.) embed. A new widget starts as a snapshot of the project catalog. Edits here stay on this widget.

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:

  • Restyle this hero without teaching a radius. Change rounded-xl here 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 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 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 declaration blocks. Publish is how they reach the published (and dev.) embed.

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.

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.

SurfaceWhat happens
Composer canvas and PreviewAlways 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 classes or click Sync from Project. Viewing CSS settings is a separate, lower permission. A viewer sees the catalog and no edit button.

Put rounded-xl on the hero

You do this on the widget management page — the same place you open the composer. If the project already has rounded-xl, Sync is faster than typing it again. If only this hero should own the class, add it here.

  1. Open the organization → team → project → environment → Maya's homepage hero (or the Default Widget).
  2. Click CSS Settings. You land on Widget CSS Settings.
  3. On the Class Definitions card, click Edit Classes (or View Class Definitions to scan first). The Sync from Project button also lives on this catalog page and on the hub.
  4. To pull the project template: click Sync from Project. To add a class only this widget needs: open Edit, click Add class, type rounded-xl and its declaration block, then Save Changes.
  5. In the composer, click the hero column. Open CSS Classes and type rounded-xl.
  6. On Staging or Production, publish when the live embed should carry the new rules. Development streams the draft — see Development Integration.

That is a safe stop. The canvas already shows the radius. Publish is how visitors see it. Syncing updates the draft only — the live site does not move until she publishes again.

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 name is the contract. RuleCMS attaches the class and nothing more. It does not invent a fallback if the name is missing from both the catalog and the host page. A typo looks like an unset class: nothing paints, with no error banner. Names you also define here preview on the canvas. Host-only names wait until the widget is on a page that already compiled them.

This widget's catalog

Open the widget, then CSS Settings, then Class Definitions. This catalog belongs to this widget.

To change the template for the next widget you create, edit the project catalog.

Class names on this widget can point at the declaration blocks you define here.

  • 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.

The full widget catalog is included — not “only classes this widget uses.” Same choice as CSS variables. Tree-shaking a utility catalog is easy to get wrong once values nest var() and @supports.

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 Class Definitions 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 permissionusers 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 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.

You do not have to choose between RuleCMS and your stylesheet. Most teams keep the same names in both places. RuleCMS includes this widget's catalog on the published embed by default so the embed looks correct even on a page that has not compiled those utilities yet. If that double definition is a problem, turn inclusion off.

Turn off inclusion on published widgets

On this widget's Class Definitions 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 classes 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 define these 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.
Composer and Preview still use this widget's catalog when inclusion is off. Authors keep seeing classes while they work. Only the published (and Development-draft) embed on your site stops carrying the extra rules. CSS variables have their own, independent toggle. Empty catalog plus the setting on is the same as off: there is nothing to include.
If you turn inclusion off, the host must define every class the widgets use. A missing utility on the live page paints as nothing — the same as a typo. 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 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.

A column that carries rounded-xl paints that radius on the canvas while you edit. Publish is how those draft rules 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 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 ClassesPutting names on columns, rows, and widgets. This catalog is what those names mean.
  • CSS VariablesThe token catalog. Independent inclusion toggle. A class body can call var(--radius-xl); the token still lives there.
  • Class DefinitionsThe project template for the next widget you create.
  • WidgetsCSS Settings on the widget management page: this catalog, Sync from Project, and Generate Tailwind.
  • Cascade layersWrap these class rules in @layer utilities or @layer components so they compete with host Tailwind instead of steamrolling it. A class body cannot contain a top-level @layer.
  • Generate Tailwind CSSCompile 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.
  • FontsThe typeface catalog. A class body cannot register a face.
  • ThemesHost-condition overrides for the same --names. A class can nest @media; a token cannot.
  • CSS PropertiesTyped @property so tokens those classes transition can interpolate.
  • Keyframes, Position try, and Widget chromethe rest of the project templates on the same home. Widget copies sit under Widgets in the sidebar.
  • Projects, Environments & TokensThe pipeline, and project CSS as the template for the next widget.
  • Development IntegrationPreview the live Development draft with a dev. token.
  • HTML Custom Element EmbedThe script tag that mounts a widget in light DOM so host utilities still reach it.
  • API ReferenceThe 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 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 “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.