The host owns color-scheme. The widget still looks light
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. They are separate settings.
When to use it / when not to
| You want | Without widget chrome | With this catalog |
|---|---|---|
| A dark widget on a light host (or the reverse) so form controls match the embed | Native inputs stay the host scheme. Selection is the browser default. | RuleCMS sets color-scheme on the widget root. Inputs paint dark (or light) with the widget. |
A host that already sets color-scheme on a wrapper around the embed | Works today. Do not double-set it. | Turn published inclusion off. Composer and Preview still use the catalog. |
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.
- Preview dumps the catalog as emitted CSS on
.rulecms-render-surface > *— not a:rootlisting. - Edit is one card: color scheme, selection colors, caret, and scrollbar fields. Pick
darkor typevar(--brand-accent)— not a pasted stylesheet. Invalid values are refused. Catalog Save is explicit.
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.
What the renderer does with the catalog
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, and later project edits do not come back here.
| 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. |
When the widget catalog is applied, chrome lands on .rulecms-render-surface > * in composer and on [data-rulecms-published-key] > * on the embed, plus that subtree's ::selection. RuleCMS never styles :root, html, or body.
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.
- 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 the default.
- 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.
color-scheme on a wrapper around the embed. A missing scheme on the live page leaves native inputs on the host default.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, CSS variables, class definitions, fonts, keyframes, themes, and typed properties — not folded into itemList. The field is widgetChrome.
{
"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.
The React renderer (@rulecms/widget-react) and the HTML custom element 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;
}Good to know
- This catalog belongs to this widget. Copy and promote copy these columns as-is. The project catalog is only the starting snapshot.
- At most one chrome entry. This catalog is a single card, not a list.
- Themes change tokens. Chrome changes native controls. A dark theme can set
--background. Chrome is what makes a<select>paint dark without stylinghtml. - React and the HTML embed this pass. Other framework SDKs do not emit this catalog yet.
- Leave the catalog empty if the host already sets color-scheme around the embed. Nothing extra is sent to the published page.
Related docs
- Themes — token overrides under a dark host; chrome tells native controls how to look
- CSS Variables — tokens chrome can point at, such as
var(--brand-accent) - Fonts — the same apply-to-widget slot
- Class Definitions — utility bodies; a class can nest
::selectionif you put it on every root box - Cascade layers — wrap those class rules in
@layer utilitiesor@layer components - CSS Properties — typed
@property - Keyframes — named
@keyframes - Position try — named
@position-tryfallbacks - HTML Custom Element Embed
- Development Integration
- API Reference — the public widget GET, including
widgetChrome - Projects, Environments & Tokens
- The Composer