Marrow

Embedding diagrams

Put a Marrow diagram inside an existing docs site, wiki, or portal — and keep it current in CI.

A built diagram is an HTML document, not a fragment. That makes embedding simple and slightly opinionated: you embed it in an <iframe> rather than splicing its markup into your page.

The basic embed

<iframe
  src="/diagrams/branch.html"
  title="Branch office network"
  loading="lazy"
  style="width:100%; height:640px; border:1px solid #27272a; border-radius:12px"
></iframe>

That's the whole integration. The diagram brings its own styles and runtime, so it cannot collide with your site's CSS — which is exactly why the iframe is the right container rather than a limitation.

Height matters more than width. A network diagram in a 300 px strip is unreadable; 560–720 px is a sensible range for an inline figure, and a full-viewport height for a dedicated page.

Embed and kiosk modes

When embedding a diagram inside a dashboard, blog post, or kiosk display, strip extraneous chrome using URL query parameters or hash fragments:

<!-- Minimal chrome: hides breadcrumbs, search dock, sidebar, and backdrop frame -->
<iframe
  src="/diagrams/branch.html?chrome=minimal"
  title="Branch Office Network"
  style="width:100%; height:600px; border:none;"
></iframe>

Marrow executes an inline script synchronously in the <head> to evaluate parameters before first paint, preventing flashes of unstyled content (FOUC). Both query strings and hash fragments are supported:

Parameter / Hash FlagValuesEffect
chromeminimal | fullminimal hides topbar breadcrumbs, search dock, sidebar rail, and backdrop strip. Controls and canvas remain interactive.
inspectoroff | onoff suppresses the right-hand inspector drawer on device or link click.
minimapoff | onoff hides the bottom-right minimap navigation overlay.
themedark | lightOverrides the authored topology theme for the session.
export_titleon | offControls whether title blocks are stamped during static PNG/SVG exports.
export_legendon | offControls whether dynamic auto-legends are generated during static exports.
export_portson | offControls whether interface port chips are forced visible during exports.
#embed=truebooleanShortcut equivalent to ?chrome=minimal.

Flags can be combined via query parameters (?chrome=minimal&inspector=off&minimap=off) or hash fragments (#chrome=minimal;inspector=off;minimap=off). Because hash changes do not reload the page, parent frames can toggle UI chrome dynamically by modifying iframe.src = url + '#chrome=minimal'.

Embedded Marrow Diagram with minimal chrome (#embed=true)
Minimal Chrome Embed (#embed=true): Topbar, search dock, and sidebars stripped; floating canvas controls activeZero FOUC sync

Theme

A diagram carries the theme from its topology's theme: key — dark, light, or auto. In an embed you usually want it to match the host page, and there are two ways to get that without rebuilding the file.

Follow the viewer's OS — theme: auto

theme: auto

The diagram resolves dark/light from the viewer's prefers-color-scheme before it paints, and switches live if they change it. No host code — a sensible default for a diagram embedded on a site that itself follows the OS.

Follow the host's own toggle — postMessage

If your page has its own light/dark switch, drive the diagram over postMessage. The diagram announces itself when its runtime is ready; answer with the theme, and re-send whenever yours changes:

const frame = document.querySelector('iframe.marrow');

addEventListener('message', (e) => {
  if (e.data?.type === 'marrow:ready') sendTheme();   // answer the ready ping
});
function sendTheme() {
  const theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
  frame.contentWindow.postMessage({ type: 'marrow:theme', theme }, '*');
}

The message is { type: 'marrow:theme', theme: 'dark' | 'light' | 'auto' }. A host-driven theme overrides the baked theme: and isn't persisted — opening the same file standalone still shows its authored theme — and it works whatever theme: the file was built with.

Bi-Directional postMessage Protocol

Bi-Directional postMessage Protocol

Event bus connecting parent portal (Next.js/React) with isolated Marrow diagram

Host Application
Next.js Portal, NOC Dashboard, Documentation Wiki
postMessage({ type: 'marrow:theme', theme: 'dark' })
postMessage({ type: 'marrow:select', deviceId: 'sw-01' })
marrow:ready
marrow:theme
marrow:select
marrow:selected
Marrow Diagram Iframe
Offline self-contained viewer with hash flags
parent.postMessage({ type: 'marrow:ready' })
parent.postMessage({ type: 'marrow:selected', id })

React & Next.js component (<MarrowEmbed />)

For Next.js, Vite, or React applications, install the official shadcn-compatible <MarrowEmbed /> component (also exported as <TopologyCanvas />):

npx shadcn@latest add https://marrowdiagram.vercel.app/r/topology-canvas.json

Or import directly from @marrow/ui:

import { MarrowEmbed } from '@marrow/ui';

export function NetworkDashboard() {
  return (
    <MarrowEmbed
      src="/diagrams/campus.html"
      title="Campus Core Network"
      chrome="minimal"
      inspector="on"
      minimap="on"
      theme="auto"
      height="650px"
      floatingActions={true}
    />
  );
}

The component automatically manages:

  • Zero FOUC theme synchronization: Subscribes to host DOM dark/light mutations and forwards theme updates via postMessage.
  • Fullscreen Mode: Integrated camera action button toggling full-viewport presentation.
  • Standalone Launch: Direct external link button to open the self-contained diagram in a dedicated tab.
  • Hash Flag Pre-boot: Sets minimal chrome, inspector, and minimap flags before the initial paint.

Linking straight to a device

Multi-page sites have real URLs, so you can deep-link:

/diagrams/global/index.html              overview
/diagrams/global/zones/campus.html       one zone
/diagrams/global/devices/core-sw-01.html one device
/diagrams/global/inventory.html          the table

Linking to a device page from a runbook is often more useful than embedding the whole diagram.

Content Security Policy

The output loads nothing external, so a strict policy is fine. What it does need is inline styles and inline script — everything is inlined by design:

frame-src 'self';

on the host page, and for the diagram document itself:

default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'; script-src 'unsafe-inline';

If your platform forbids inline script outright, single-file output won't run there. Use the multi-page site shape instead — its runtime and styles are separate files under assets/, which a hash- or nonce-based policy can handle.

JavaScript Client API (window.marrow)

When embedded in a portal or host application, Marrow exposes a global window.marrow object for programmatic camera navigation, element selection, lifecycle events, and dynamic inspector tabs.

Programmatic camera and selection

Host portals can control focus and camera movement directly:

// Focus a specific device and smoothly fly camera to it
window.marrow.selectDevice('core-sw-01', true);

// Select an interconnect cable and open the link inspector
window.marrow.selectLink('core-sw-01--dist-sw-01#0');

// Clear selection and close inspector drawer
window.marrow.deselect();

// Retrieve currently inspected item
const current = window.marrow.getSelected();
// returns { type: 'device', item: DataDevice } | { type: 'edge', item: DataLink } | null

// Open the "About this diagram" document modal
window.marrow.openAbout();

Event listeners

Subscribe to viewer interactions using marrow.on(event, handler):

// Listen for selection changes (device or link)
const unsubSelect = window.marrow.on('select', (selection) => {
  console.log('Selected:', selection.type, selection.item.name ?? selection.item.id);
});

// Listen for deselection
const unsubDeselect = window.marrow.on('deselect', () => {
  console.log('Selection cleared');
});

// Listen for inspector tab switches
const unsubTab = window.marrow.on('tabchange', (tabId) => {
  console.log('Active inspector tab:', tabId);
});

Dynamic custom inspector tabs

Host applications can inject custom tabs into the inspector drawer dynamically at runtime:

// Register a live telemetry tab with custom DOM rendering and cleanup
const unregister = window.marrow.inspector.registerTab({
  id: 'live-telemetry',
  label: 'Live Telemetry',
  target: 'device', // 'device' | 'edge' | 'both'
  order: 40,
  visible: (device) => device.role === 'router',
  render: (device, container) => {
    container.innerHTML = `
      <div class="m-inspector-section">
        <h3>Live Traffic (${device.name})</h3>
        <p>Inbound: 14.2 Gbps · Outbound: 9.8 Gbps</p>
      </div>
    `;
    const interval = setInterval(() => {
      // Poll or update live stats
    }, 2000);

    // Return cleanup callback invoked when tab is closed or selection changes
    return () => clearInterval(interval);
  },
});

Keeping it current in CI

The diagram is generated, so treat it like any other build artifact. A minimal GitHub Actions job:

.github/workflows/diagrams.yml
name: diagrams
on:
  push:
    paths: ['input/**.yaml']

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 24 }
      - run: npm ci && npm run compile
      - run: npm run marrow -- icons fetch --yes      # if you use cloud packs
      - run: npm run marrow -- validate input/*.yaml  # exit 1 on errors
      - run: npm run marrow -- build input/branch.yaml -o site/diagrams/branch.html
      - uses: actions/upload-artifact@v4
        with: { name: diagrams, path: site/diagrams }

validate exits 1 on validation errors and 2 on usage or I/O errors, so it gates a pipeline correctly with no extra scripting. Add --json if you want to post the diagnostics somewhere.

Because output is deterministic, committing the built HTML produces a clean diff: the file only changes when the topology actually changed.

Interactive Demo Studio & UI Suite

Explore real, interactive compiled diagrams across all layout archetypes in our live Interactive Demo Studio.

To build custom network dashboards and network operating centers (NOCs) with these components, browse the Marrow UI Component Catalog.