Skip to main content

Iframe Theming

Chastify sends theme information to iframe extensions so your UI can match the lock page where it is embedded.

Do not assume the user is using a dark theme. Your extension can be opened in light, dark, custom, translucent, and high-contrast looking themes.

tip

The safest default is to treat the iframe ui payload as the source of truth for page background, text color, and border color, then derive your own readable panels and badges from ui.isDark.

Theme Payload

When Chastify opens your iframe, it appends a URI-encoded JSON payload to location.hash.

The payload includes ui:

{
"bridge": {
"v": 1,
"nonce": "request-nonce",
"parentOrigin": "https://chastify.net"
},
"sessionId": "extension-session-id",
"lockId": "lock-id",
"ui": {
"isDark": true,
"bg": "rgb(10, 10, 10)",
"text": "#e5e7eb",
"border": "#27272a"
}
}

Field meanings:

  • ui.isDark: whether the host page is currently using a dark-looking theme.
  • ui.bg: page/surface background from the host. This may be a color or a complex CSS background.
  • ui.text: primary readable text color.
  • ui.border: border color that matches the host theme.
caution

ui.bg can be a full CSS background string, not only a hex color. Use it as a CSS background value, not as a value you parse manually.

Parse The Hash

type ChastifyThemePayload = {
ui?: {
isDark?: boolean;
bg?: string;
text?: string;
border?: string;
};
};

export function parseHashPayload(): ChastifyThemePayload | null {
const raw = window.location.hash.startsWith("#")
? window.location.hash.slice(1)
: window.location.hash;

if (!raw) return null;

try {
return JSON.parse(decodeURIComponent(raw));
} catch {
return null;
}
}

Minimal CSS Variables

Convert the payload to CSS variables once on startup:

const payload = parseHashPayload();
const ui = payload?.ui ?? {};
const isDark = ui.isDark !== false;

document.documentElement.style.setProperty("--chastify-bg", ui.bg || (isDark ? "#040711" : "#f8fafc"));
document.documentElement.style.setProperty("--chastify-text", ui.text || (isDark ? "#e5e7eb" : "#0f172a"));
document.documentElement.style.setProperty("--chastify-border", ui.border || (isDark ? "#27272a" : "#e2e8f0"));
document.documentElement.dataset.themeMode = isDark ? "dark" : "light";

Use those variables in your app shell:

html,
body,
#root {
min-height: 100%;
margin: 0;
}

body {
background: var(--chastify-bg);
color: var(--chastify-text);
}

.panel {
border: 1px solid var(--chastify-border);
}

React Example

import { useMemo } from "react";

function getTheme(payload: any) {
const ui = payload?.ui ?? {};
const isDark = ui.isDark !== false;

return {
isDark,
pageStyle: {
background: ui.bg || (isDark ? "#040711" : "#f8fafc"),
color: ui.text || (isDark ? "#e5e7eb" : "#0f172a"),
} satisfies React.CSSProperties,
panelClass: isDark
? "border-white/10 bg-black/35 text-slate-100"
: "border-slate-200 bg-white/85 text-slate-950 shadow-sm",
mutedClass: isDark ? "text-slate-400" : "text-slate-600",
badgeClass: isDark
? "bg-sky-400/15 text-sky-200"
: "bg-sky-50 text-sky-900 ring-1 ring-sky-200",
};
}

export function ExtensionApp({ payload }: { payload: any }) {
const theme = useMemo(() => getTheme(payload), [payload]);

return (
<main style={theme.pageStyle} className="min-h-screen p-4">
<section className={`rounded-2xl border p-4 ${theme.panelClass}`}>
<h1 className="text-xl font-bold">My Extension</h1>
<p className={`mt-2 text-sm ${theme.mutedClass}`}>
This text stays readable on light and dark Chastify themes.
</p>
<span className={`mt-3 inline-flex rounded-full px-3 py-1 text-xs font-semibold ${theme.badgeClass}`}>
Connected
</span>
</section>
</main>
);
}

Tailwind Pattern

If you use Tailwind, branch from ui.isDark for every surface that contains text.

const isDarkTheme = payload?.ui?.isDark !== false;

const panelClass = isDarkTheme
? "border-white/10 bg-black/30 text-slate-100"
: "border-slate-200 bg-white/85 text-slate-950 shadow-sm";

const successClass = isDarkTheme
? "border-emerald-300/20 bg-emerald-400/10 text-emerald-100"
: "border-emerald-500/25 bg-emerald-50 text-emerald-950";

const errorClass = isDarkTheme
? "border-rose-300/20 bg-rose-400/10 text-rose-100"
: "border-rose-500/25 bg-rose-50 text-rose-950";

Use the chosen classes where the text is rendered:

<div className={`rounded-xl border px-3 py-2 text-sm ${result.ok ? successClass : errorClass}`}>
<div className="font-semibold">{result.title}</div>
<div className="mt-1 opacity-85">{result.message}</div>
</div>

Live Theme Updates

The parent may send live theme updates while your iframe is open:

type UiUpdateMessage = {
type: "chastify:ext:ui";
v: 1;
nonce: string;
ui: {
isDark?: boolean;
bg?: string;
text?: string;
border?: string;
};
};

Listen for that message and verify the nonce:

const nonce = payload.bridge.nonce;
const parentOrigin = payload.bridge.parentOrigin;

window.addEventListener("message", (event) => {
if (event.origin !== parentOrigin) return;

const msg = event.data as Partial<UiUpdateMessage>;
if (msg.type !== "chastify:ext:ui" || msg.v !== 1) return;
if (msg.nonce !== nonce) return;

applyTheme(msg.ui);
});

Common Mistakes

  • Hardcoding text-white or pale text-*-100 inside cards without a light-theme alternative.
  • Styling the page background but forgetting nested cards, badges, progress rings, and result feeds.
  • Treating ui.bg as a simple hex color. It may be a gradient or browser-normalized CSS background string.
  • Using native form controls without setting both background and text color. Select options can become unreadable in dark themes.
  • Assuming an iframe reload is needed for theme changes. Listen for chastify:ext:ui if you want live updates.
warning

Theme data is presentation data only. Do not use it for authorization, trust decisions, unlock logic, rewards, punishments, or backend session identity.

Checklist

Before publishing your iframe extension:

  • Test light theme.
  • Test dark theme.
  • Test a custom theme if available.
  • Verify all cards, buttons, badges, form controls, progress rings, and result messages have readable contrast.
  • Verify text still fits on mobile.
  • Verify the iframe auto-resizes after theme or content changes.

Next: Iframe Quickstart or API Examples.