delegates rendering of a block to a plain JavaScript function. znai creates a DOM node and hands your function: the DOM node to render into the parameters from markdown (as ) a so rendering can react to light/dark theme switches themeBox my custom content box custom value See the sample function at the end of this page. include-javascript-function args themeObservable :include-javascript-function: themeBox { label: "my custom content box", customKey: "custom value", }
Attach your function to under the name you want to reference, and load the script via : window extensions.json (function () { window.themeBox = function (node, args, themeObservable) { // draw inside `node` // read initial theme via `themeObservable.current` // subscribe to updates via `themeObservable.subscribe(listener)` }; })();
Your function receives three arguments: the parent to append content to. the parameters passed from markdown. Framework-level keys (, , , , ) are handled by znai and not forwarded. live access to the current znai theme. The function may return a cleanup function. znai calls it when the block is removed or re-rendered dispose timers, external listeners, or other resources here. The sample function below returns a cleanup that logs to the browser console: Open DevTools, then navigate away from this page every rendered above is unmounted and its cleanup runs. node div args title wide className anchorId height themeObservable { current: "light" | "dark", subscribe(listener) { // listener is called with the new theme name on every change } } return function cleanup() { console.log("themeBox cleanup: block removed from the page"); }; themeBox
themeBox Switch the theme to see me update. demo theme Toggle the theme using the switcher in the bottom left corner and watch the custom content restyle. The hookup is one line: themeObservable.subscribe(applyTheme);
Pass to render a title bar above the block. znai owns the title via its shared container, so your function never sees it. themeBox theme aware box rendered inside a titled container title :include-javascript-function: themeBox { title: "theme aware box", message: "rendered inside a titled container" }
Pass to span the full page width, matching wide images and iframes. themeBox wide theme aware box true this block breaks out of the single-column width and spans edge to edge of the page container, giving long values plenty of horizontal room to stretch before they need to wrap onto a second line wide demo edge-to-edge theme 1024 42 0.003 wide: true :include-javascript-function: themeBox { title: "wide theme aware box", wide: true, note: "this block breaks out of the single-column width and spans edge to edge of the page container, giving long values plenty of horizontal room to stretch before they need to wrap onto a second line", tags: ["wide", "demo", "edge-to-edge", "theme"], metrics: {"requests": 1024, "latency_ms": 42, "error_rate": 0.003} }
By default the block grows to fit whatever the function renders into it. Pass to pin the block to a fixed size content past it scrolls inside the viewport znai gives the function. accepts either a number (treated as pixels) or any CSS length string like or . The function below appends one row per event. Without , all twelve rows render and the block grows to fit them: activityFeed deploys 09:14 build started commit a31f9b on main 09:17 tests passed 248 / 248 green 09:18 image pushed registry.io/web@sha256:c2... 09:19 deploy started rolling out to canary 09:21 canary healthy p99 42ms, error rate 0.0% 09:24 promoted 100% of fleet now on a31f9b 09:31 alert fired latency spike on shard 4 09:33 shard restarted shard 4 back to baseline 09:40 config reload feature flag billing-v2 on 09:47 backfill queued 1.2M rows over 6 batches 09:58 backfill complete elapsed 11m02s 10:05 deploy started rolling out to prod-eu Add and the same twelve events scroll inside a fixed-size box instead: activityFeed deploys 220 09:14 build started commit a31f9b on main 09:17 tests passed 248 / 248 green 09:18 image pushed registry.io/web@sha256:c2... 09:19 deploy started rolling out to canary 09:21 canary healthy p99 42ms, error rate 0.0% 09:24 promoted 100% of fleet now on a31f9b 09:31 alert fired latency spike on shard 4 09:33 shard restarted shard 4 back to baseline 09:40 config reload feature flag billing-v2 on 09:47 backfill queued 1.2M rows over 6 batches 09:58 backfill complete elapsed 11m02s 10:05 deploy started rolling out to prod-eu height height "320px" "30vh" activityFeed height :include-javascript-function: activityFeed { title: "deploys", events: [ {time: "09:14", action: "build started", detail: "commit a31f9b on main"}, {time: "09:17", action: "tests passed", detail: "248 / 248 green"}, ... ] } height :include-javascript-function: activityFeed { title: "deploys", height: 220, events: [/* same twelve events */] }
Styling With A Class Name
Pass to attach an arbitrary class to the parent node znai creates. Use it to decorate the wrapper without touching the function: themeBox highlighted by an outer border theme-box-with-border The dashed outline comes from the class on the outer wrapper the box itself is untouched. className :include-javascript-function: themeBox { label: "highlighted by an outer border", className: "theme-box-with-border" } .theme-box-with-border { padding: 6px; border: 2px dashed var(--znai-color-green); border-radius: 6px; } .theme-box { box-sizing: border-box; width: 100%; padding: 12px 16px; border: 1px solid var(--znai-snippets-outer-border-color, #dedede); border-left: 4px solid var(--znai-color-blue); border-radius: 4px; background-color: var(--znai-snippets-title-background-color, #efeff1); color: var(--znai-regular-text-color); font-family: var(--znai-regular-font-family); } .theme-box.theme-dark { border-left-color: var(--znai-color-yellow); } .theme-box-title { color: var(--znai-section-title-color); font-weight: 600; font-size: 0.95rem; margin-bottom: 8px; } .theme-box-list { display: grid; grid-template-columns: auto 1fr; column-gap: 12px; row-gap: 4px; font-family: var(--znai-code-font-family, monospace); font-size: 0.9rem; } .theme-box-empty { font-style: italic; color: var(--znai-regular-text-light-color); } .theme-box-key { color: var(--znai-brand-primary-color); font-weight: 600; } .theme-box-value { color: var(--znai-regular-text-color); word-break: break-word; white-space: pre-wrap; } .theme-box-current-theme { font-size: 0.8rem; margin-top: 10px; color: var(--znai-regular-text-light-color); }
Values passed via flow into znai's local search. Searching for any word inside the parameters surfaces the page and the rendered block. args
The function below powers the examples on this page. It reads the initial theme and subscribes to future changes. /* * custom javascript global function to render custom content within znai * generated guides * * renders a panel that lists every argument passed via the plugin as * a key/value row. reacts to znai theme switches via the theme observable * to toggle a `theme-dark` class that drives the accent color via CSS. */ (function () { function formatValue(value) { if (value === null || value === undefined) { return String(value); } if (typeof value === "object") { return JSON.stringify(value); } return String(value); } function createElement(tagName, className, textContent) { var el = document.createElement(tagName); el.className = className; if (textContent !== undefined) { el.textContent = textContent; } return el; } window.themeBox = function (node, args, themeObservable) { var box = createElement("div", "theme-box"); var titleEl = createElement( "div", "theme-box-title", "args passed to the function" ); var list = createElement("div", "theme-box-list"); var themeEl = createElement("div", "theme-box-current-theme"); var argKeys = Object.keys(args); if (argKeys.length === 0) { list.appendChild( createElement("div", "theme-box-empty", "no args passed") ); } else { argKeys.forEach(function (key) { list.appendChild(createElement("div", "theme-box-key", key)); list.appendChild( createElement("div", "theme-box-value", formatValue(args[key])) ); }); } box.appendChild(titleEl); box.appendChild(list); box.appendChild(themeEl); node.appendChild(box); function applyTheme(themeName) { box.classList.toggle("theme-dark", themeName === "dark"); themeEl.textContent = "current theme: " + themeName; } applyTheme(themeObservable.current); themeObservable.subscribe(applyTheme); return function cleanup() { console.log("themeBox cleanup: block removed from the page"); }; }; })(); /* * sample plugin showing a list of rows that grows to fit its content. * * the parent node sized by znai (`height` arg) is what constrains us when * unset, the feed renders all rows tall enough to show them all; when set, * the same content scrolls inside the fixed viewport znai gave us. */ (function () { function createElement(tagName, className, textContent) { var el = document.createElement(tagName); el.className = className; if (textContent !== undefined) { el.textContent = textContent; } return el; } function buildRow(event) { var row = createElement("div", "activity-feed-row"); row.appendChild(createElement("span", "activity-feed-time", event.time || "")); row.appendChild(createElement("span", "activity-feed-action", event.action || "")); row.appendChild(createElement("span", "activity-feed-detail", event.detail || "")); return row; } window.activityFeed = function (node, args, themeObservable) { var events = Array.isArray(args.events) ? args.events : []; var feed = createElement("div", "activity-feed"); events.forEach(function (event) { feed.appendChild(buildRow(event)); }); node.appendChild(feed); function applyTheme(themeName) { feed.classList.toggle("activity-feed-dark", themeName === "dark"); } applyTheme(themeObservable.current); themeObservable.subscribe(applyTheme); }; })();