Skip to main content

Flex Inline

Flex Inline puts the experience directly on your page — no iframe, no nested document. You paste a two-line snippet, and flex-client.js renders the experience into an open shadow root attached to your marker <div>.

<div
data-flex-inline
data-flex-manifest-url="https://acme.ceros.site/spring-launch/manifest.v1.json"
data-embed-height="auto"
></div>
<script src="https://assets.ceros.site/js/flex-client.js"></script>

Get the snippet from Embed in Flex, or from the /experiences/{resourceId}/embed-codes endpoint in the API reference.

Public beta

Flex Inline is in public beta.

How it works

  1. flex-client.js scans the page for <div data-flex-inline> elements.
  2. For each one, it fetches the experience's manifest — the JSON document at data-flex-manifest-url that describes the pre-rendered HTML, styles, webfonts, and scripts for one page of the experience.
  3. It attaches an open shadow root to your <div>, injects a CSS reset and the manifest's styles into it, and writes the experience markup inside.
  4. It registers the experience's custom elements, hydrates, and starts interactions.
  5. A MutationObserver keeps watching your page, so markers added later — by a single-page app after navigation, say — are picked up automatically.

Sizing

The experience width is sized to its container. Give the marker <div> the width you want and the experience scales to fit.

You have two options for the experience height:

  1. Keep data-embed-height="auto" on the <div> if you'd like the container to adjust automatically to the full height of the content.
  2. Add a height style tag, style="height: 200px;", to the <div> to use a fixed size and force the experience content to scroll.

Why a shadow root

It's the middle ground between an iframe and dumping markup into your page.

  • Your CSS can't leak in. The experience renders exactly as designed, even if your site sets * { box-sizing: content-box } or styles every <p>.
  • The experience's CSS can't leak out. Its component styles stay scoped to the shadow root and never repaint your navigation or footer.
  • It's still one document. No iframe scroll-jail, no separate window, no postMessage for basic things. The experience participates in your page's layout and scrolling like any other element.

The shadow root is open, so you can reach into it from your own scripts (el.shadowRoot) if you need to.

Multiple experiences on one page

One marker per experience, one script tag:

<div
data-flex-inline
data-flex-manifest-url="https://acme.ceros.site/spring-launch/manifest.v1.json"
data-embed-height="auto"
></div>

<div
data-flex-inline
data-flex-manifest-url="https://acme.ceros.site/q3-report/manifest.v1.json"
data-embed-height="auto"
></div>

<script src="https://assets.ceros.site/js/flex-client.js"></script>

Each hydrates independently, with its own interactions.

Multi-page experiences

A multi-page experience swaps pages in place, without reloading your page. Links authored in the experience are intercepted by a small client-side router.

Those links are written as query parameters on your URL, not Ceros':

?cer_<experienceSlug>=<pageSlug>

So a link to the pricing page of spring-launch renders as ?cer_spring-launch=pricing. This is deliberate: the experience is a component on a page it doesn't own, so middle-click, cmd-click, and "Copy link address" have to resolve against your URL. Someone who opens such a link in a new tab lands on your page with the experience already on the right page.

If your server or router does anything with query parameters, leave cer_* parameters alone.

What Ceros does not do

Ceros never touches your page's <head> or <body>. Two consequences:

  • Custom head/body HTML you attached to the experience in Flex is not emitted. Analytics snippets, consent scripts, fonts, and meta tags you configured there won't run. Put them on your page instead.
  • SEO, meta, and OpenGraph tags are yours to provide. The experience isn't in your initial HTML, so it doesn't contribute to indexing either. If that matters, use Flex SSR.

Analytics

The experience dispatches its analytics events directly onto your page — the same events the iframe embed SDK delivers, with no iframe and no postMessage in between. Subscribe with window.flexAnalytics:

window.flexAnalytics.on(({ experience, analyticsEvent }) => {
gtag('event', analyticsEvent.type, analyticsEvent.data)
})

See Analytics events for event names, payload shapes, and filtering.

Scripting the experience

To drive components from your own JavaScript, use the Flex Experience SDK. On Flex Inline the experience lives inside a shadow root, so pass the container to connect() — the SDK reaches through for you:

<script type="module">
import { connect } from 'https://assets.ceros.site/js/flex-experience-sdk.js'

const experience = await connect(document.querySelector('[data-flex-inline]'))
experience.findByLocator('hero-video').media.play()
</script>

Because page changes are in-place swaps, your handle goes stale on every swap. Re-connect on the flex.page.change window event — see the SDK's delivery modes page.

When to use it

  • You want the experience to feel like part of your page, with no iframe.
  • Adding a <script> tag is all the access you have to the host page.
  • You can live without analytics inside the experience for now.

When to reach for something else

  • You need the experience in your initial HTML for SEO or LCP → Flex SSR.
  • You need analytics or error monitoring inside the experience today → iframe embed.