# Preview a theme on your site

The [home page](/) shows a theme on apps we made up. Far more convincing is a theme on a page that's actually yours, but that only works if your page takes part.

:::note[This is live]
The home page's **"On your own site"** panel is this mechanism, running for real. Type in a page of yours that implements "Your side" below, and it gets the look you're currently wearing the moment it announces itself, nothing bundled to stand in for it, and nothing pre-approved: point it at anything that speaks the contract below.
:::

**A URL box alone isn't enough.** A page inside an `<iframe>` can't be styled from outside it. CSS custom properties don't cross that boundary, the parent can't reach into the frame's document, and most sites refuse to be framed at all. So the framed page has to render the theme *itself*; the box on the home page only ever hands it one, your page does the rest.

## The conversation

Two messages, both checked by origin:

```
your page  →  its parent   { source: "mode.place", type: "ready" }
our page   →  your frame   { source: "mode.place", type: "theme", expression, record }
```

Your page says it's listening; we send the current theme, and send a fresh one whenever the visitor picks a different look. The `record` is an ordinary [`place.mode.standard.theme`](https://standard.mode.place/docs/reference/schema/), the same thing you'd fetch from someone's account.

## Your side

```ts
// The only origin we'll accept a theme from.
const HOST = "https://standard.mode.place";

// Rule 5: declare the layer order once, anywhere in your CSS.
//   @layer app, author, viewer;
// And mark the scope this applies to, e.g. <body data-mode="default">.

const style = document.createElement("style");
document.head.append(style);

window.addEventListener("message", (event) => {
  if (event.origin !== HOST) return;
  const data = event.data;
  if (data?.source !== "mode.place" || data?.type !== "theme") return;

  // themeToCss is the reference renderer from "Render themes in your app".
  style.textContent = fontFaces(data.record) + themeToCss(data.record, data.expression, "viewer");
  document.body.dataset.mode = data.expression;
});

// Tell the host we're listening. (Only meaningful when framed.)
if (window.parent !== window) {
  window.parent.postMessage({ source: "mode.place", type: "ready" }, HOST);
}
```

`themeToCss` and `fontFaces` are the four small functions in [Render themes in your app](https://standard.mode.place/docs/guides/render-themes/); nothing else is needed. Your page ends up with one `<style>` element in the viewer layer, exactly as if a signed-in visitor had brought their own look.

Two things worth doing while you're there:

- **Allow the frame.** If you send `X-Frame-Options` or a `frame-ancestors` policy, add `https://standard.mode.place` to it, or the frame won't load at all.
- **Check the origin both ways.** The snippet above ignores messages from anywhere else, and only announces itself to us. Do keep both checks: a look can only ever set the standard's known values to CSS strings, but an unchecked message channel is worth nobody's trouble.

## Next steps

- **[Render themes in your app](https://standard.mode.place/docs/guides/render-themes/)**: the renderer this uses, and how to do the same thing with real accounts instead of a frame.
- **[The Mode Contract, Rule 5](https://standard.mode.place/docs/contract/#rule-5--whose-look-wins-fill-through-not-conflict)**: why the theme goes in the viewer layer.