Host-page variables: --forumulate-host-* and the floatRoom contract

Embed
Preview of the card
<a href="https://forumulate.com/guides/host-page-variables"><img src="https://forumulate.com/guides/_api/public/orgs/046a9bc0-d530-43ae-9bae-af7945008cda/spaces/36aa4df0-1e95-4cef-8ae9-795169441345/pages/8dfbebb4-eaae-4a2e-8cd3-ccd762842482/social-card.svg?v=3e7ae33eec8b" alt="Host-page variables: --forumulate-host-* and the floatRoom contract" width="600" height="315"></a>

Every reader page we serve publishes its resolved brand as eight CSS custom properties on :root, so a widget that is not ours — a chat bubble, a consent banner, a referral card, any product's embed — can dress itself in the page's brand without knowing our internal token names (which change when we refactor). The names are a contract; the internal ones are not.

Variable

What it is

Unset theme value (light / dark)

--forumulate-host-accent

The brand colour (primaryColor, primaryColorDark in dark)

#4818f0 / #8d6eff

--forumulate-host-accent-ink

Readable text on an accent fill (near-white or near-black)

#fbfaf6 / #14171b

--forumulate-host-surface

The page colour (palette.background)

#f3f1ea / #14171b

--forumulate-host-ink

Body text (palette.ink)

#15181c / #e9e5db

--forumulate-host-line

Rules and borders (palette.line)

16% ink

--forumulate-host-radius

Corner radius (components.radius), in px

8px

--forumulate-host-font

The body font stack the page paints with

the system stack

--forumulate-host-focus-ring

A complete outline value: <width>px solid <colour>

2px solid the accent

The older names still work. These variables first shipped as --fd-* (--fd-accent, --fd-accent-ink, … --fd-focus-ring). Every page still publishes those too, with the same values, so a widget built against them needs no change. Read the --forumulate-host-* names in anything new.

How they are set:

  • Server-rendered, no flash. The values are literals from the theme the page resolves (space theme over site theme over brand file, see Theme your site), written in the same <style> as the rest of the theme. A widget that reads getComputedStyle(document.documentElement) on its first frame already sees the brand.

  • Dark mode follows the page: an OS preference the page does not override, or an explicit <html data-theme="dark">.

  • --forumulate-host-focus-ring is never absent and never invisible. It is set even on a page whose theme says nothing (the default is a 2px ring in the accent). An owner's components.focus.color is used as given; otherwise the accent when it reaches 3:1 against the page colour (WCAG 1.4.11), otherwise the page's ink. A theme cannot turn it off: the width floor is 1px. Our own focus styles draw with it, so a widget that uses it matches ours.

The website SDK reads them from your page

The same names work the other way round. If your page publishes them — by hand, or because your design system does — every @forumulate/embed element and mount function fills accent, radius and font from --forumulate-host-accent, --forumulate-host-radius and --forumulate-host-font when you leave those unset:

:root {
  --forumulate-host-accent: #0f766e;
  --forumulate-host-radius: 12px;
  --forumulate-host-font: 'Inter', system-ui, sans-serif;
  --forumulate-host-focus-ring: 2px solid #0f766e;
}
<forumulate-widget org="acme" space="docs" mode="ask"></forumulate-widget>

For each variable the SDK reads the --forumulate-host-* name first, then --fd-*, then --forgedeck-host-*, and uses the first one your page sets. A page written against an older spelling keeps working unchanged.

Precedence per option: what you pass → your page's variable → the widget's default. hostVars: false (or the host-vars="false" attribute) turns the middle step off. The hosted scripts (/embed/*.js) do the same for their accent when no data-accent is given.

While a consent banner covers the bottom of the viewport, --forumulate-safe-bottom on <html> says by how many pixels. We write the same value under its first name, --fd-safe-bottom, which consent platforms such as Cookie Munch already read and write. Every floating element adds it to its bottom, reading our name first:

.my-launcher {
  position: fixed;
  bottom: calc(20px + var(--forumulate-safe-bottom, var(--fd-safe-bottom, 0px)));
}

Ours all do: the ask-AI button and panel, the shop cart, the forum timeline pill, the auto-refresh toast, the preview ribbon, the hosted ask, FAQ and changelog launchers, and the room a floating chat embed reserves at the foot of the page.

Where the value comes from:

  • A consent manager we adapt (Cookie Munch, OneTrust, Cookiebot, Osano, Usercentrics, Didomi, TCF, …): our adapter measures its banner and writes the variable as it moves.

  • A CMP that implements window.__fdConsent natively, or a host page that wrote one: we read __fdConsent.layout.bottomInset() and follow layout.on('change'), and mirror the value onto both names.

  • Your own sticky footer: set --forumulate-safe-bottom (or --fd-safe-bottom) in your stylesheet. At 0 we remove our inline value rather than writing 0px, so yours is never overridden by a banner that is not there.

The safe-bottom inset is layout, not theme: it is never in a theme and never set when no banner is visible.

The Forumulate teamUpdated