Block preview page
/dms-preview is a public, hidden page of the DMS that renders a tree of registered DMS blocks without the dashboard header or sidebar. These docs embed it in an iframe for their live previews, and you can open it to try blocks without writing a page. Set transparent: true when the embedding page should supply the background.
Enable it
The preview is enabled automatically in AntelopeJS development mode (GetRuntimeInfo().dev). Outside that mode, opt in with docsPreview: true or DMS_DOCS_PREVIEW=1:
dms: {
source: { type: "package", package: "@antelopejs/dms", version: "<latest>" },
config: {
docsPreview: true,
// …
},
},
DMS_DOCS_PREVIEW=1 ajs project dev -w
The page then answers at <clientBaseUrl>/dms-preview — http://localhost:3001/dms-preview in development. The @antelopejs/dms playground enables it.
These conditions are alternatives: docsPreview: false does not disable the preview in framework development mode or when the environment opt-in is set. NODE_ENV does not determine the framework's development mode. The DMS evaluates the gate when constructing the module; restart the backend after changing it, then restart the frontend to pick up the new configuration.
Render a tree from the URL
Pass the tree in ?state=, as base64url-encoded JSON:
interface PreviewState {
tree: PreviewNode | PreviewNode[];
theme?: "light" | "dark";
locale?: string; // a locale the dashboard knows, e.g. "fr-FR"
padding?: boolean; // pads the blocks off the frame's edges; true by default
transparent?: boolean; // paints no background, so the embedding page shows through
}
interface PreviewNode {
type?: string; // a block type, e.g. "KpiCard"
componentName?: string; // or its component, e.g. "dms-kpi-card"
options?: Record<string, unknown>;
children?: Array<PreviewNode & { id?: string; slot?: string; colSpan?: number }>;
}
const state = {
tree: { type: "KpiCard", options: { title: "Revenue", staticValue: 48200, staticDelta: 12.4, showDelta: true } },
theme: "dark",
};
const encoded = Buffer.from(JSON.stringify(state)).toString("base64url");
console.log(`http://localhost:3001/dms-preview?state=${encoded}`);
The block catalog decides what may be drawn. A node names a registered type — which maps to its component — or a componentName that belongs to a registered type. Anything else, a block that needs a backend class (a TableView, a ResourceForm), and any URL option (fetchUrl, submitUrl…) that is not a relative path of the same site render an inline notice instead of the block, never an arbitrary component.
Drive it with postMessage
Embedded in an iframe, the page talks to its parent window. Every message carries channel: "dms-preview".
| Direction | Message | Meaning |
|---|---|---|
| iframe → parent | { channel, type: "ready" } | Mounted; send render now. |
| parent → iframe | { channel, type: "render", tree, theme?, locale?, transparent? } | Draw this tree. transparent is kept from the previous state when absent. |
| parent → iframe | { channel, type: "theme", theme } | Switch between light and dark. |
| iframe → parent | { channel, type: "resize", height } | The content is now this tall, in CSS pixels. |
| iframe → parent | { channel, type: "error", message } | Something could not be drawn. |
const frame = document.querySelector<HTMLIFrameElement>("#preview")!;
const CHANNEL = "dms-preview";
window.addEventListener("message", (event) => {
if (event.source !== frame.contentWindow || event.data?.channel !== CHANNEL) return;
if (event.data.type === "ready") {
frame.contentWindow!.postMessage(
{ channel: CHANNEL, type: "render", tree: { type: "Banner", options: { tone: "info", title: "Hello" } } },
"*",
);
}
if (event.data.type === "resize") frame.style.height = `${event.data.height}px`;
});
By default the page paints the dashboard's own background, which shows as a lighter or darker rectangle on a page of another colour. Set transparent: true (in ?state= or a render message) to paint none: the blocks then sit on the embedding page's background. A transparent preview declares the colour scheme of its theme, and a browser keeps an iframe opaque when that scheme differs from the embedding page's: send the theme matching the page's mode (dark on a page with color-scheme: dark).
In your own components
The page renders the tree with DmsBlockPreview, a global component of the dashboard. Hand it a tree to draw blocks inside any custom component — the dms-ai chat does exactly that:
<template>
<DmsBlockPreview :tree="{ type: 'EmptyState', options: { title: 'No invoices yet' } }" padding />
</template>
Outside /dms-preview, the component fetches the list of drawable block types from GET /api/block-preview/types, which requires a signed-in member.
Next steps
Design fidelity
What an agent does with your mockup — one framing question, then component by component, a preview of the DMS block next to a sketch of the custom one, and the choice between keeping, adjusting or replacing the block.
Ship a skill with your module
Teach coding agents (Claude Code, Codex) and the dms-ai assistant how to use your module by shipping a SKILL.md next to its code, declared in package.json.