Contents

Widget chrome

This is the chrome catalog on Maya's homepage hero — this widget, not the project. Themes change token values. Chrome tells the browser how native controls look. Composer and Preview emit these draft color-scheme and selection fields, so a dark host still shows dark inputs. 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. Themes can darken --brand-surface when her product puts dark on html. The email field and the highlighted caption on that hero still look light. Host color-scheme on body does not paint native inputs inside the widget. This catalog is color-scheme and selection on the widget root.

Two everyday jobs:

  • Darken native controls on this hero. Pick dark here. The email field and selection follow. Edits stay on this widget. They do not write back to the project.
  • Pull later project chrome onto this widget. When the template moves, Sync from Project copies those fields here. Values that exist only on this widget stay if the project left them unset.

That same card covers selection colors, caret, and scrollbars when the brand needs polish on the same root. Color scheme is the field that changes how a <select> and an email input paint. Themes change token values. Chrome tells the browser how native controls look. They do not replace each other.

How widget chrome thinks

The published-key wrapper is display: contents. It does not generate a box. Host body or :root can set color-scheme: dark, a custom selection color, and scrollbar styles — and native inputs inside the widget still paint light. A utility class on one inner node does not cover the whole embed. Selection stays the browser default.

A class definition can nest &::selection if someone puts that class on every root box. Authors will not. Fonts already solved “apply to the widget” with one flag. Widget chrome is that same slot for color-scheme, selection, caret, and scrollbars.

Themes change token values under a dark host. Chrome tells the browser how native controls should look. A dark --brand-surface does not make a <select> paint dark. You still need color-scheme on the widget root.

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 these draft fields. Publish is how they reach the published (and dev.) embed.

Here is the case that surprises people. Maya puts color-scheme: dark on body and expects the email field on the hero to paint dark. It does not. The published-key span is display: contents. Host color-scheme never reaches the inputs inside. This catalog sets color-scheme on the widget root boxes — > * — so those controls follow the widget.

A second surprise: she sets dark on the project and expects the Production Default Widget — the hero she already built — to pick up dark inputs. 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 third surprise: she overrides --brand-surface under html.dark on Themes and expects the email field to flip with the surface. Themes change tokens. Chrome is what makes a native control paint dark without styling html.

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

One chrome entry on this widget — not “only fields this widget uses.” Same choice as fonts and CSS variables.
Color scheme is the important field. Selection, scrollbar, and caret are brand polish on the same page so you do not grow a fifth catalog later.

Set color-scheme so native controls match the hero

You do this on the widget management page — the same place you open the composer. If the project already has color-scheme: dark, Sync is faster than filling the card again. If only this hero should own the chrome, set 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 Widget chrome card, click Edit Widget chrome (or View Widget chrome 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 set chrome only this widget needs: open Edit, pick dark for Color scheme, add selection or caret tokens if the brand needs them, then Save Changes.
  5. On Staging or Production, publish when the live embed should carry the new chrome. Development streams the draft — see Development Integration.

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

Color-scheme, selection, caret, and scrollbar fields

Edit is one card — not a pasted stylesheet. Reach for each field when the job matches:

FieldWhen you reach for it
Color schemeThe important field. Native inputs follow this. light, dark, or light dark. Pick dark when the hero is dark and the host is not — or the reverse.
Selection background and Selection colorBrand polish for highlighted text. A token such as var(--brand-accent) and #fff. Same sanitizer as CSS Variables: no url(, no @, no braces, no ;.
Caret colorThe text-insertion caret on the email field. Another color or token — the same sanitizer.
Scrollbar width and Scrollbar colorWidth is auto, thin, or none. Color is auto, or two colors such as var(--brand-accent) transparent.
Themes is a different tool. Themes change tokens. Chrome tells native controls how to paint. They do not replace each other.

This widget's catalog

Open the widget, then CSS Settings, then Widget chrome. This catalog belongs to this widget.

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

Native controls on this widget follow the color-scheme and selection you set here.

  • Preview dumps the catalog as emitted CSS on .rulecms-render-surface > * — with that class in place of the published-key selector. That listing is for authors. It is not a :root listing, and it never paints html.
  • Edit is one card: color scheme, selection colors, caret, and scrollbar fields. Pick dark or type var(--brand-accent) — not a pasted stylesheet. Invalid values are refused. Save Changes stays disabled until the catalog is clean.

A typical emit looks like this:

[data-rulecms-published-key="env---w"] > * {
  color-scheme: dark;
  caret-color: var(--brand-accent);
  scrollbar-width: thin;
  scrollbar-color: var(--brand-accent) transparent;
}
[data-rulecms-published-key="env---w"] > * ::selection {
  background: var(--brand-accent);
  color: #fff;
}

Color values reuse the CSS variable sanitizer: no url(, @, braces, or ;. Color scheme is light, dark, or light dark. Scrollbar width is auto, thin, or none. An empty card is no entry. At most one chrome entry — this catalog is a single card, not a list.

A new widget starts as a copy of the project chrome catalog. Until someone saves fields here (or the project had none to copy), this widget relies on the host. A dark host then still leaves native inputs light inside the embed.

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 Widget chrome 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 sets color-scheme too — and that is fine

The original model is still the right one when the host already sets color-scheme on a wrapper around the embed: Maya's product stylesheet owns the scheme, and RuleCMS widgets inherit it. A Next.js host that already painted those controls is one of those cases — not a requirement, and not something you configure inside RuleCMS.

Teams that also fill the RuleCMS catalog usually keep the same scheme in both places. By default RuleCMS still includes those fields on the published widget. The rest of your page is untouched: injection applies chrome under the published key. It never sets properties on html, :root, or body.

That default is the useful one when:

  • The widget is on a page that never set color-scheme around the embed (a landing page, a partner site, an email-adjacent HTML shell).
  • Editors need the published embed to match Preview even if the host forgot native-control styles.
  • You are still rolling dark chrome out on the host and the catalog is ahead of the stylesheet.

It is the wrong default when the host wrapper is the only definition 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 scheme in both places. RuleCMS includes this widget's chrome on the published embed by default so the embed looks correct even on a page that never set color-scheme around the widget. If that double definition is a problem, turn inclusion off.

Turn off inclusion on published widgets

On this widget's Widget chrome 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 this widget chrome when published widgets are rendered. A non-empty catalog is sent with the widget and the renderer applies it. This is how RuleCMS makes Preview and the live embed match.
  • My apps already set color-scheme and selection styles. Do not include them again when the widget is being rendered. Published widgets still carry token names that need those colors, but RuleCMS does not ship the chrome rules. Your host stylesheet must set them. Use this when the app already wraps the embed with color-scheme.
Composer and Preview still use this widget's catalog when inclusion is off. Authors keep seeing the chrome while they work. Only the published (and Development-draft) embed on your site stops carrying the extra rules. Empty catalog plus the setting on is the same as off: there is nothing to include.
If you turn inclusion off, the host must set color-scheme on a wrapper around the embed. A missing scheme on the live page leaves native inputs on the host default. 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 fields on that same response as a sibling of the widget, its collections, CSS variables, class definitions, fonts, keyframes, themes, and typed properties — not folded into itemList, not copied onto each collection. The field is widgetChrome so the renderer can emit color-scheme and selection on the widget root without guessing which list is type, and without touching your html element.

{
  "success": true,
  "data": {
    "widget": { "publishedKey": "…", "itemList": "…", "…": "…" },
    "childCollections": { },
    "widgetChrome": {
      "colorScheme": "dark",
      "selectionBackground": "var(--brand-accent)",
      "selectionColor": "#fff",
      "caretColor": "var(--brand-accent)",
      "scrollbarColor": "var(--brand-accent) transparent",
      "scrollbarWidth": "thin"
    }
  }
}

widgetChrome 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 includeWidgetChrome: false so renderers that still receive an object must not emit it. Missing on the payload means include (legacy clients). The widget still loads. You never lose a page because a chrome field 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> on the widget root, for example:

[data-rulecms-published-key="env---w"] > * {
  color-scheme: dark;
}
[data-rulecms-published-key="env---w"] > * ::selection {
  background: var(--brand-accent);
  color: #fff;
}

Nested collections inherit. Other widgets on the same page keep their own catalogs — each rule is scoped to that widget's published key.

The Development draft fetch (Development Integration) reads the same fields from the draft widget row, so a dev. fallback preview on your site matches composer.

The canvas already emits the rules when the card is saved. Publish is how those draft fields 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 color-scheme still works. Widgets that never used RuleCMS chrome 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 tokens chrome can point at, the theme overrides that change those tokens, the other catalogs, and the request your app already sends live on the pages below.

  • ThemesToken overrides under a dark host. Themes change tokens. Chrome tells native controls how to look.
  • CSS VariablesTokens chrome can point at, such as var(--brand-accent). The light (or default) value lives there.
  • FontsThe same apply-to-widget slot; another catalog that lands on the widget root instead of :root.
  • Class DefinitionsUtility bodies; a class can nest ::selection if you put it on every root box. Authors will not.
  • CSS PropertiesTyped @property so tokens can interpolate. This page does not type those names.
  • KeyframesNamed @keyframes; the same name-plus-body pattern.
  • Widget chromeThe project template for the next widget you create.
  • Position try, Cascade layers, and Themesthe rest of the project templates on the same home. Widget copies sit under Widgets in the sidebar.
  • Generate Tailwind CSSCompile the class names this widget already uses. That flow does not replace this catalog. It is not this page.
  • 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 color-scheme still reaches it — except native inputs inside the embed, which still need this catalog.
  • API ReferenceThe public widget GET, including widgetChrome.
  • MDN: color-scheme

How this fits

A widget is the content. Themes override --brand-surface when the host is dark. This catalog sets color-scheme and selection on the widget root so native controls match the embed. It does not replace the widget, it does not replace Maya's stylesheet, and it never paints html. Themes change tokens. Chrome does not. Publishing is how a draft catalog reaches a Staging or Production token.

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.

  • Themes change tokens. Chrome changes native controls. A dark theme can set --background. Chrome is what makes a <select> paint dark without styling html.
  • Leave the catalog empty if the host already sets color-scheme around the embed. Nothing extra is sent to the published page.
  • Host color-scheme on body does not reach inputs inside the widget. The published-key wrapper is display: contents. That is the surprising case, not a slower flip.