The frame bridge
Your page runs in an <iframe> inside Facia. The bridge lets it get the token and theme, and tell Facia when it's ready, when to open the chat, and when to close.
#The frame
<iframe sandbox="allow-scripts allow-forms allow-same-origin"
allow="fullscreen" referrerpolicy="no-referrer" src="https://your-app…/?facia_token=…">- No popups and no top-level navigation. Open links inside your page, or use
Facia.openChat(). - Fullscreen is allowed (for games).
- Facia shows a loading skeleton over the business's background until you call
ready(). - The frame stays alive while the client switches tabs and comes back (up to 3 apps at once), so a game or form doesn't restart.
- Facia accepts messages only from your frame's window and your page's origin, and posts only to your origin.
#facia-app.js
<script src="https://facia.chat/app/facia-app.js"></script>| API | What it does |
|---|---|
Facia.inFacia | true when the page is inside Facia |
Facia.ready() | Removes Facia's loading skeleton. Call it once your content is drawn |
Facia.token() | The latest token (posted by Facia, or ?facia_token= from the address) |
Facia.onToken(fn) | Called with each new token, and right away if there is one |
Facia.onTheme(fn) | Called with { scheme, vars, radius, style } now and on every theme change |
Facia.applyTheme(t) | Writes the theme's CSS variables onto <html> and sets color-scheme, data-facia-scheme, data-facia-radius and the style attributes |
Facia.onInsets(fn) | Called with { top, bottom, left, right } safe-area insets in px |
Facia.openChat() | Opens the client's support chat with the business |
Facia.close() | Goes back to where the client came from |
Facia.resize(h) | Accepted and ignored: the frame always fills the tab |
The usual start:
Facia.onTheme((t) => Facia.applyTheme(t));
Facia.onInsets((i) => (document.body.style.paddingBottom = i.bottom + "px"));
const token = Facia.token();
// …draw…
Facia.ready();#Theme variables
applyTheme sets these on <html>, so your CSS can use var(--accent):
| Variable | Use for |
|---|---|
--bg | Page background |
--panel, --surface, --elev | Panels, cards, raised surfaces |
--hover, --press | Hover and pressed states |
--border | Lines and outlines |
--text, --text-2, --text-3 | Main, secondary and faint text |
--accent, --accent-2, --accent-fg, --accent-soft | Primary actions, a second accent, text on accent, soft accent backgrounds |
--success, --warn, --danger | Good, careful and bad states |
--chat-bg | The chat background |
radius is soft or sharp. Style for it with [data-facia-radius="sharp"]. scheme is light or dark.
#Matching the style
The theme also says which style the client is in: style: { id, base, outline, grid }. id is classic, futuristic or custom:<id>, and base is classic or future. In Futuristic, and in custom styles built on it, Facia draws everything as lit outlines on a faint grid. These extra variables carry it:
| Variable | Use for |
|---|---|
--fx-deep | The style's background |
--fx-surface | Cards |
--fx-line | Lines and outlines |
--fx-grid | The background grid's lines |
--gold-500, --win-500 | Prizes and wins |
applyTheme sets data-facia-style and data-facia-base on <html>, plus data-facia-outline and data-facia-grid when they apply. A few rules make your page match:
html[data-facia-outline] .card { background: transparent; box-shadow: inset 0 0 0 1px var(--fx-line); }
html[data-facia-outline] .primary { background: transparent; color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
html[data-facia-grid] body {
background-image: linear-gradient(var(--fx-grid) 1px, transparent 1px), linear-gradient(90deg, var(--fx-grid) 1px, transparent 1px);
background-size: 30px 30px;
}In Classic none of these attributes are set, so these rules change nothing.
#Making it feel native
- Paint
--bgfrom the first frame (html { background: var(--bg) }and a sensible fallback) to avoid a white flash. - The frame fills the tab and owns the scroll. Don't add outer scrollbars.
- Keep the first load small. A plain or Phaser page is instant, while a Unity WebGL build (10–30 MB) shows the skeleton for a while on mobile data.
- Use
--accentfor your main action and neutral surfaces for the rest. Gold means VIP in Facia, so avoid it unless you mean VIP. - Show the client's alias. Never ask for their name.
#Raw messages
If you'd rather not load the script, speak postMessage directly:
| Direction | type | Payload |
|---|---|---|
| page → Facia | facia:ready | – |
| page → Facia | facia:openChat | – |
| page → Facia | facia:close | – |
| page → Facia | facia:resize | { height } (ignored) |
| Facia → page | facia:token | { token } |
| Facia → page | facia:theme | { scheme, vars: { "--bg": "…", … }, radius, style } |
| Facia → page | facia:insets | { top, bottom, left, right } |
const send = (type, extra) => parent.postMessage({ type, ...extra }, "*");
addEventListener("message", (e) => {
if (e.source !== parent) return;
if (e.data?.type === "facia:token") useToken(e.data.token);
if (e.data?.type === "facia:theme") applyVars(e.data.vars);
});
send("facia:ready");Facia sends facia:token, facia:theme and facia:insets right after facia:ready, and sends facia:theme again whenever the theme changes.
#Testing outside Facia
Open your page directly in a browser. Facia.inFacia is false, there's no theme, and Facia.token() reads ?facia_token= if you add one. For a real token, register the app with a http://localhost address and open it from inside your business.