> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inklyai.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Sharing and embedding

> Send the link, frame the page, open it as a pop-up, or render the demo inside your React app.

Two questions decide how a demo gets in front of someone. Does it run as its own page or inside your React app? And for the page, who hosts it?

## Which do you want?

| | On its own | Inline in a page | As a pop-up |
| - | - | - | - |
| **The demo page** | Send the link | `<iframe>` | `embed.js` |
| **Your React app** | — | `<Demo>` | `<DemoModal>` |

For the page, the second question is who hosts it:

| Who hosts the page | How | What you get |
| - | - | - |
| The hosting service | `interactive-demo publish` | A URL, nothing to deploy |
| You | `interactive-demo build`, then upload `dist/` | A URL on your own static host |

The snippets below are the same either way. Only the origin in them differs. [Publish and self-host](/open-source/publish-and-self-host) covers both routes.

**The top row is the same page used three ways.** Send someone the URL and they get the demo full-screen. Frame that URL and it sits in your page. Point the loader script at it and it opens over your page. Nothing is installed, and the embedding page can be React, Vue, Rails, or plain HTML.

**The bottom row skips the page.** You install the runtime package and render the player inside your own React tree, so there is no iframe and no second document.

Pick the top row if you want isolation, or if the demo lives on a different host from the site showing it. Pick the bottom row if you want the demo to behave like part of your app, with your router, your state, and your styling around it. An iframe costs you a second document and cross-document messaging for size and events. The component costs you the player in your bundle.

<Warning>
  The two rows need different things deployed. The top row needs the page at a URL: published, or `dist/` from `build` on your host. The bottom row does not use `dist/` at all. It needs the demo folder (`demo.config.json` and `assets/`) reachable as static files, plus `@inkly-org/interactive-demo` installed.
</Warning>

## Send the link

The demo page is complete on its own. The simplest thing you can do with a demo is send someone its URL: the one `publish` printed, or `dist/<slug>/` on your host. It is the same URL the iframe and the pop-up point at.

Add `?autoplay=1` to start playing as soon as it loads.

## Inline: the iframe

Add `?embed=inline` to the page URL and the page renders the player alone: no bar, no canvas, a transparent background, and the player filling the frame edge to edge.

Size the frame to the demo. That is its screen ratio, plus the player's header (52px, or 0 when the demo sets `chrome.hideHeader`), plus 2px for the card's border:

```html theme={"dark"}
<div style="container-type: inline-size; width: 100%; max-width: calc(max(0px, 80vh - 54px) * 1440 / 900); margin: 0 auto;">
  <div style="position: relative; width: 100%; height: calc(100cqw * 900 / 1440 + 52px + 2px);">
    <iframe
      src="https://your-site.com/demos/onboarding/?embed=inline"
      title="Onboarding demo"
      loading="lazy"
      allow="clipboard-read; clipboard-write; fullscreen"
      allowfullscreen
      style="position: absolute; inset: 0; width: 100%; height: 100%; border: 0;">
    </iframe>
  </div>
</div>
```

You do not have to work the numbers out. `interactive-demo embed` prints this snippet with the numbers filled in from the demo. So do `publish`, `build` (with a placeholder host), and the editor's Share dialog.

The outer `max-width` keeps the frame under 80% of the viewport height. The inner box is the exact ratio plus header, so the player never letterboxes.

* `allow="fullscreen"` lets the player's fullscreen button work inside the frame.
* `loading="lazy"` keeps the player off the critical path of the host page.
* Below about 640px wide, the player switches to its mobile layout.
* Without `?embed=inline` the page keeps its own bar and canvas. That is what you want for a link, not an embed.

The player never reads or writes anything outside its own document. It makes no network requests beyond loading its own files and assets. The one exception is a form widget with a `submitTo` URL, which POSTs its values there.

## Pop-up: the loader script

To open a demo from a button, include the loader once and call `InteractiveDemo.open` with the demo's URL. The loader is three kilobytes with no dependencies. `build` writes it to `dist/embed.js`, and the hosting service serves the same file at `/embed.js`.

```html theme={"dark"}
<script>window.InteractiveDemo=window.InteractiveDemo||{q:[],open:function(){(this.q=this.q||[]).push(arguments)}};</script>
<script src="https://your-site.com/demos/embed.js" async></script>

<button onclick="InteractiveDemo.open('https://your-site.com/demos/onboarding/')">Try the demo</button>
```

The first line is a stub that queues clicks made before the script arrives.

`open` draws a scrim and a centred frame and loads the page inside it with `?embed=inline`. It closes on Escape, on a click outside the frame, or on `InteractiveDemo.close()`. The frame starts at 16:9 and takes the demo's own ratio as soon as the page [reports its size](/open-source/runtime#embed-mode). A relative URL is resolved against the host page.

`interactive-demo embed --mode popup` prints the loader and a button for HTML, React, Next.js, Vue, and Svelte.

## Listen for events from an embedded demo

A framed demo, inline or in the pop-up, relays every runtime event to the page that embeds it as a `message` of type `interactive-demo:event`. Form submissions, step views, completion, and button clicks all arrive this way, so the host page can react without touching the demo:

```html theme={"dark"}
<script>
  window.addEventListener('message', (e) => {
    if (e.data?.type !== 'interactive-demo:event') return;
    const event = e.data.event;
    if (event.type === 'form_submit') {
      // event.fields is [{ id, label, value }]
    }
  });
</script>
```

Check `e.origin` against the host you embed from before trusting the payload. A demo that stands on its own page sends nothing.

The React component gets the same events through its `onEvent` prop. The [event list](/open-source/runtime#events) is in the runtime reference. A form can also POST its values to a URL of your choosing with [`submitTo`](/open-source/authoring#form-submissions).

## In your React app

If the host page is React, skip the iframe and render the player inline. Copy the demo's source folder (`demos/<slug>/`, not the built one) into your app's static files, such as `public/` in Next.js or Vite, and point the player at it:

```tsx theme={"dark"}
import { Demo } from '@inkly-org/interactive-demo';
import '@inkly-org/interactive-demo/styles.css';

<Demo src="/demos/onboarding/" />
```

The component fetches `demo.config.json` from that folder and resolves the media paths in it against the same folder. To import the config at build time instead, pass the object and say where its folder is served from:

```tsx theme={"dark"}
import config from './demos/onboarding/demo.config.json';

<Demo src={config} baseUrl="/demos/onboarding/" />
```

Media served from somewhere else, such as a CDN, goes through `resolveAssetUrl={(path) => …}`, which receives each relative path.

The component takes the same config the static page embeds, so a demo authored in the editor works in both places without changes.

### A pop-up in React

Wrap the player in `DemoModal` rather than loading `embed.js`. It renders the player in-process through a portal with the same overlay, so there is no iframe and no second copy of the runtime.

```tsx theme={"dark"}
import { useState } from 'react';
import { Demo, DemoModal } from '@inkly-org/interactive-demo';

function TryTheDemo() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <button onClick={() => setOpen(true)}>Try the demo</button>
      <DemoModal open={open} onClose={() => setOpen(false)} label="Onboarding demo">
        <Demo src="/demos/onboarding/" />
      </DemoModal>
    </>
  );
}
```

`DemoModal` mounts its children only while open, so the demo starts from the beginning each time. It hands focus back to the button on close.

Every prop is in the [runtime reference](/open-source/runtime).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.