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.
Flex Inline is in public beta.
How it works
flex-client.jsscans the page for<div data-flex-inline>elements.- For each one, it fetches the experience's manifest — the JSON document at
data-flex-manifest-urlthat describes the pre-rendered HTML, styles, webfonts, and scripts for one page of the experience. - 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. - It registers the experience's custom elements, hydrates, and starts interactions.
- A
MutationObserverkeeps 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:
- Keep
data-embed-height="auto"on the<div>if you'd like the container to adjust automatically to the full height of the content. - 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
postMessagefor 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.