Embedding
Drop your docs into an iframe and restyle them per-embed with a URL fragment — no build, no config, no same-origin requirement.
An undocs site can be embedded anywhere with a plain <iframe>. To make it look
like it belongs there, append a theme fragment to the URL:
https://docs.example.com/guide#~eyJwIjoiIzBlYTVlOSJ9Everything after #~ is a base64url-encoded JSON object of design tokens. The
page reads it before the first paint and applies it as CSS custom properties, so
there is no flash of the default theme.
The fragment is the deliberate choice here: browsers never send it to the server, so it changes nothing about caching, SSR, or prerendering, and — unlike injecting a stylesheet from the parent page — it works cross-origin. The embedding page needs no access to the iframe's document.
#Tokens
| Key | CSS property | Example |
|---|---|---|
p | --primary | #0ea5e9 |
pf | --primary-foreground | #ffffff |
br | --brand | #0ea5e9 |
bg | --background | #ffffff |
fg | --foreground | #0b0b0c |
mu | --muted | #f4f4f5 |
muf | --muted-foreground | #71717a |
bd | --border | #e4e4e7 |
c | --card | #ffffff |
r | --radius | 0.5rem |
w | --ui-container | 72rem |
f | --font-sans | Inter, ui-sans-serif, system-ui |
Two more keys are not tokens:
d— an object of the same keys, applied in dark mode only.m— pins the color mode:"l"(light) or"d"(dark). The site's light/dark toggle hides itself when the mode is pinned, since it would do nothing.
{
"p": "#0ea5e9",
"bg": "#ffffff",
"d": { "p": "#38bdf8", "bg": "#0b0b0c" },
"m": "l"
}Any value you leave out keeps the site's own styling — this is an override layer, not a full theme, so a two-key payload is perfectly normal.
#Building the fragment
Base64url-encode the JSON and prefix it with #~. No dependencies:
const encode = (theme) =>
"#~" + btoa(JSON.stringify(theme)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
const src = "https://docs.example.com/guide" + encode({ p: "#0ea5e9", m: "l" });In Node, swap btoa(...) for Buffer.from(JSON.stringify(theme)).toString("base64").
Tip
Working in the undocs repo? node scripts/embed-theme-url.mjs '{"p":"#0ea5e9"}'
prints the fragment and validates the payload against the same rules the
site applies, so a typo'd key fails loudly instead of being silently dropped.
--url <base> prints a full URL and --decode <fragment> reads one back.
#In an iframe
<iframe
src="https://docs.example.com/guide#~eyJwIjoiIzBlYTVlOSIsIm0iOiJsIn0"
title="Documentation"
style="width: 100%; height: 100%; border: 0"
></iframe>The payload is stripped from the URL as soon as it is read, so the address the visitor can copy stays clean. A real anchor still works alongside it — put it in front of the separator:
https://docs.example.com/guide#installation~eyJwIjoiIzBlYTVlOSJ9#Limits
A URL anyone can craft must not be able to do anything but recolour the page, so the fragment is narrow by design:
- Only the keys in the table above are applied. Unknown keys are ignored, so check your spelling — a wrong key fails silently rather than erroring.
- Values are capped at 64 characters, and the whole payload at 2048.
- Values containing
url(),image-set()orelement()are rejected, so the fragment cannot make the page fetch anything. - Values are set through the CSSOM rather than as CSS text, so a crafted value cannot escape into arbitrary styling.
This customises colors, radius, container width and font — it cannot change layout, hide navigation, or inject content. For that, ship a custom theme.