Contents

Position try

Maya already has a homepage hero. The SALE badge on that photo should stay on screen when the viewport is tight — position-try-fallbacks: --flip, or the built-in flip-block her overlay class already names. A class that only says position-try-fallbacks: --flip is a pointer. Built-ins belong in Class Definitions. This page is the project catalog: the template of custom --flip try sets copied onto each new widget. After that the widget owns the copy. Editing here does not restyle widgets that already exist.

What you can do

You already opened Maya's homepage hero from Widgets and started composing. The SALE badge on that photo should stay on screen when the viewport is tight — position-try-fallbacks: --flip, or the built-in flip-block her overlay class already names. A class that only says position-try-fallbacks: --flip is a pointer. Built-ins never lived in this catalog.

Two everyday jobs:

  • Register --flip once for the next widget. Add the dashed ident and the declarations here. A widget she creates after that starts with the try set already filled in.
  • Leave inclusion off when the host already ships --flip. Her product stylesheet can own the at-rule. The badge still points at --flip. RuleCMS does not ship a second copy.

That same pattern covers a copied overlay class that already says flip-block, a host design system that names --flip, and a custom try set the host never compiled. Authors stay in the catalog form. Engineers keep one vocabulary.

How a position-try catalog thinks

A class can store position-try-fallbacks: flip-block or position-try-fallbacks: --flip. Built-ins such as flip-block and flip-inline already belong in Class Definitions. Someone still has to define a custom name. The host stylesheet can do that — Maya's product already might, in a design-system sheet. RuleCMS can do it too, in a catalog of dashed ident → declarations.

Do not add flip-block to this catalog. It is a built-in. A class body cannot hold a custom try set either: @position-try inside [published-key] .name { … } does not register, and a token cannot hold the block (@ is rejected). That is why this catalog exists: RuleCMS emits @position-try --flip so Preview and the published embed match.

Authors type --flip and the declarations. RuleCMS emits the unscoped at-rule. You never paste @position-try --flip { … }.

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 try sets 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 position-try catalog itself.

Here is the case that surprises people. Maya copies an overlay class that says position-try-fallbacks: --flip onto the SALE badge and expects it to flip when it would overflow. If --flip is missing, the class is present and the badge stays where it first landed. The utility is only a pointer. This catalog is the try set.

A second surprise: she edits --flip on the project and expects the Production Default Widget — the hero she already built — to pick up the declarations. 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: @position-try is document-global. The name is not prefixed, and the rule is not scoped to the published-key span. Two embeds that define different declarations for --flip will race — same name, last widget wins. Use the same try set, or turn inclusion off on the host that already owns the name. RuleCMS does not rename try sets per published key.

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.

SurfaceWhat happens
Composer canvas and PreviewDo 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 try set and flip the inclusion radios. Those radios set the default copied onto new widgets. They do not change widgets that already exist.

The full catalog is copied onto a new widget — not “only fallbacks this widget uses.” Same choice as CSS variables and class definitions.

Register --flip for the SALE badge

You do this on environment home. The Default Widget in Production already exists — it will not pick up a new try set until someone syncs. A widget she creates after this save starts with --flip already there.

  1. Open the organization → team → project → environment you want to work in.
  2. Under CSS catalog, click Position try. You land on this project page — Development, Staging, and Production share it.
  3. Open Edit. Click Add fallback. Name: --flip. Declarations: position-area: top; and inset: auto; — not @position-try --flip { … }. RuleCMS emits the at-rule. Save Changes.
  4. 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 --flip.

That is a safe stop. The next widget starts with a custom try set for the SALE badge instead of a name that never registered. Come back here when the next widget should start from a richer set of fallbacks, or open that widget's catalog when only the hero should change.

Name and declarations

Edit is a dashed ident plus a textarea for the declaration list — not a pasted @position-try wrapper. Reach for each field when the job matches:

FieldWhen you reach for it
NameThe custom ident — --flip. Must start with --, then a letter or underscore. Letters, numbers, -, _. No @, no dots, no spaces. Built-in names without -- flip-block, flip-inline — are refused. The help text shows Renders as @position-try --flip when the name is valid.
DeclarationsThe list only: position-area: top;, inset: auto;. Type those lines, not @position-try --flip { … }. Nested rules, url(, and a pasted @ wrapper are refused.
The class catalog is a different tool. A row that says position-try-fallbacks: flip-block or position-try-fallbacks: --flip in Class Definitions is only a pointer. Built-ins stay there. This catalog is who defines --flip.

The project catalog

Open an environment, then Position try. 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 try sets.

  • Preview dumps the catalog as emitted @position-try blocks — not a :root listing. That listing is for authors. Published widgets do not dump a global stylesheet onto your page beyond those unscoped at-rules.
  • Edit is a dashed ident plus a textarea for the declarations. Add fallback starts a row. Invalid names and bodies are refused per row. Save Changes stays disabled until the catalog is clean.

Each entry is one custom try set:

@position-try --flip {
  position-area: top;
  inset: auto;
}

At most 16 fallbacks and 2000 characters per body. Nested rules, url(, @, and a pasted @position-try wrapper are rejected. Built-in names without --flip-block, flip-inline — are refused as catalog names.

A project has no position-try row until someone saves for the first time. Until then, a new widget starts without RuleCMS-owned try sets and relies on the host stylesheet — or on built-ins in class bodies. A custom --flip that never registered leaves the overlay where it first landed.

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 fallback model is still the right one when the host already registers the same try sets: the host stylesheet ships @position-try --flip, and RuleCMS widgets inherit it. A Next.js host that already compiled those names 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 dashed ident in both places. By default RuleCMS still includes those try sets on the published widget. The rest of your page is untouched: injection is the unscoped at-rule, not a rewritten class.

That default is the useful one when:

  • The widget is on a page that never registered those try sets (a landing page, a partner site, an email-adjacent HTML shell).
  • Editors need the published embed to match Preview even if the host forgot the name.
  • You are still rolling the try set out on the host and the catalog is ahead of the stylesheet.

It is the wrong default when the host stylesheet is the only definition you want at runtime — especially if RuleCMS and the host might disagree for a while, or if two embeds would race on the same name.

If you only use built-ins — flip-block, flip-inline — leave this catalog empty. Those names already work inside a class body.

You do not have to choose between RuleCMS and your stylesheet. Most teams keep the same dashed ident in both places. RuleCMS copies this catalog onto each new widget by default, including the inclusion flag, so a new embed can look correct even on a page that never registered --flip. If that double definition is a problem, turn inclusion off.

Turn off inclusion on published widgets

On the project's Position try 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 position-try fallbacks when published widgets are rendered. A non-empty catalog is sent with the widget and the renderer defines the try sets. This is how RuleCMS makes Preview and the live embed match.
  • My apps already register these @position-try rules. Do not include them again when the widget is being rendered. Published widgets still carry class names that point at those fallbacks, but RuleCMS does not ship the @position-try blocks. Your host stylesheet must define them. Use this when the app already registers --flip.
Composer and Preview still use the widget catalog when inclusion is off. These radios only set the default copied onto a new widget. They do not change widgets that already exist. 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 custom ident the widgets use. A missing @position-try --flip on the live page leaves the overlay where it first landed. 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 try sets on that same response as a sibling of the widget, its collections, CSS variables, class definitions, and fonts — not folded into itemList, not copied onto each collection. The field is positionTry so the renderer can emit the at-rule without guessing which list is type.

{
  "success": true,
  "data": {
    "widget": { "publishedKey": "…", "itemList": "…", "…": "…" },
    "childCollections": { },
    "positionTry": {
      "--flip": "position-area: top;\ninset: auto;"
    }
  }
}

positionTry 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 includePositionTry: 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 try-set 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> of unscoped @position-try rules, for example:

@position-try --flip {
  position-area: top;
  inset: auto;
}

Nested collections inherit. Other widgets on the same page keep their own catalogs — until two of them register the same name. Same name, last widget wins.

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.

After a widget is created, restyling a try set 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 try sets and built-ins in class bodies still work. Widgets that never used a RuleCMS custom name are unchanged.

React and the HTML embed this pass. Other framework SDKs do not emit this catalog yet.

Browser support is CSS anchor positioning. Older browsers ignore the at-rule and keep the first position.

What to read next

This page is the story and the first walkthrough. The class that points at a name, the overlay that needs the flip, the other catalogs, and the request your app already sends live on the pages below.

  • Class DefinitionsPut position-try-fallbacks: flip-block or position-try-fallbacks: --flip on a class body. Built-ins stay there. A custom name still needs this catalog.
  • Overlay LayersThe SALE badge on Maya's hero photo. A layer is the layout; this catalog is the custom try set that class can point at.
  • KeyframesNamed @keyframes; the same name-plus-body pattern, another unscoped at-rule.
  • FontsThe typeface catalog; another unscoped at-rule.
  • CSS PropertiesTyped @property so token values in these declarations can interpolate. This page does not type those names.
  • CSS VariablesThe token catalog those interpolable names live in.
  • widget Position tryThis widget's live catalog, Sync from Project, and the inclusion radios that actually ship with the embed.
  • Cascade layers, Themes, and Widget chromethe 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 try sets still reach it.
  • API ReferenceThe public widget GET, including positionTry.
  • MDN: @position-try

How this fits

A widget is the content. Overlay Layers put the SALE badge on the photo. This catalog gives a custom --flip name something to register. It does not replace the widget, it does not replace built-ins in class bodies, and it does not replace Maya's stylesheet. Publishing is how a draft catalog reaches a Staging or Production token. The Composer is where a class name stores the pointer at flip-block or --flip.

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.

  • Same name, last widget wins. @position-try is document-global. Two embeds that define different declarations for --flip will race. Use the same try set, or turn inclusion off on the host that already owns the name. RuleCMS does not rename try sets per published key.
  • Leave the catalog empty if the host owns every custom try set, or if you only use built-ins. Class names on elements still work. Nothing extra is sent to the published page.
  • Ask whoever owns the stylesheet which custom try sets are already compiled. A missing @position-try --flip on the live page leaves the overlay where it first landed. That is the surprising case, not a slower flip.