23. Embedding Diagrams
The hosted viewer at archlang.dev/demo is good for paste-and-go. For dashboards, ADRs, internal portals, or marketing pages, you want diagrams in your own pages. The <archlang-viewer> custom element is the answer — one <script> tag, one custom element, the diagram appears.
This chapter covers the element’s attributes, the inline-source pattern, the multi-file pattern, custom hosting, theming, and the event protocol the viewer fires back to your page.
Rule. What you embed is a derived view of the
.archsource, not a drawing you maintain separately. The text is the source of truth; the diagram is rendered from it on every load. So the embed always reflects the model — there’s no picture to keep in sync by hand, and nothing to redraw. Keep the model accurate and the embed stays accurate.
The simplest case
Section titled “The simplest case”A single .arch file fetched from a URL:
<script type="module" src="https://archlang.dev/viewer/archlang-viewer.js"></script>
<archlang-viewer src="./checkout.arch" style="width: 100%; height: 480px"></archlang-viewer>That’s it. The script registers the custom element. The element loads ./checkout.arch, parses it, renders the diagram inside a shadow-DOM-isolated iframe. The diagram is interactive — pan, zoom, click nodes, hover for details. All the LOD behavior from Chapter 20 applies.
The primary view stays clean: it shows the business call graph, not the transport plumbing underneath it. Infrastructure planes — a message broker, a network zone, logs, metrics — are modeled as aspects (shared classifications), and the viewer reveals them as overlay layers on demand. A reader toggles the broker’s aspect layout to see which calls ride which broker, then toggles it back off; the data plane surfaces only when asked, so an embedded diagram never drowns its audience in infrastructure they didn’t come for (Chapter 9).
Inline source — single file
Section titled “Inline source — single file”When the source is part of the page itself (e.g., embedded in a Markdown blog post), use the inline-script child pattern:
<archlang-viewer style="width: 100%; height: 480px"> <script type="text/arch" data-path="/main.arch"> service Orders { aspect team: "Commerce" rest_create createOrder rest_read getOrder }
service Inventory { aspect team: "Commerce" } </script></archlang-viewer>The <script type="text/arch"> is not executed by the browser — its content is read by the viewer. The data-path attribute names the in-workspace path the file should land at.
Without an explicit package.archspace, the viewer injects a default one that imports arch.backend and arch.extras so stdlib types resolve. That behavior is controlled by wrap-manifest (below).
Inline source — multi-file workspace
Section titled “Inline source — multi-file workspace”For workspaces with multiple files, declare each one with its own <script>:
<archlang-viewer wrap-manifest="false" style="width: 100%; height: 600px">
<script type="text/arch" data-path="/package.archspace"> package: my.shop use service, rest_create from arch.backend </script>
<script type="text/arch" data-path="/orders.arch"> service Orders { aspect team: "Commerce" rest_create createOrder } </script>
<script type="text/arch" data-path="/payments.arch"> service Payments { aspect team: "Payments" rest_create authorize } </script></archlang-viewer>wrap-manifest="false" disables the auto-injected manifest because you’re supplying your own. Use this whenever you have multiple files or want non-default imports.
Attributes
Section titled “Attributes”| Attribute | Default | Effect |
|---|---|---|
src | (none) | URL to fetch a single .arch file. Loaded as /main.arch in the workspace. |
host | Origin that served archlang-viewer.js | Base URL of the deployed viewer iframe. Override when you self-host. |
wrap-manifest | "true" | When true and no package.archspace is present, the viewer injects a default one. Set to "false" when supplying your own manifest. |
The element extends HTMLElement, so standard CSS sizing (style, class) applies normally. The iframe lives inside a shadow root so your page’s CSS can’t accidentally style it.
Events
Section titled “Events”The element bubbles three CustomEvents for host-page integration:
| Event | detail | When |
|---|---|---|
arch-ready | {} | The embedded iframe finished booting. The viewer is ready to receive source. |
arch-loaded | { moduleCount } | A render completed successfully. moduleCount is the resolved module count. |
arch-error | { message } | Parse or resolve failure. message is the diagnostic. |
const v = document.querySelector("archlang-viewer");v.addEventListener("arch-ready", () => console.log("viewer up"));v.addEventListener("arch-loaded", (e) => console.log("rendered", e.detail.moduleCount, "modules"));v.addEventListener("arch-error", (e) => console.error("arch error:", e.detail.message));The events are the integration surface for telemetry, error reporting, and per-page status indicators.
Self-hosting
Section titled “Self-hosting”The default host attribute is the origin that served archlang-viewer.js. So if you ship the script from your own deploy at https://internal.example.com/arch/archlang-viewer.js, the iframe loads from the same origin automatically.
To split: ship the script from one origin, run the viewer at another:
<script type="module" src="/static/archlang-viewer.js"></script>
<archlang-viewer host="https://viewer.internal.example.com" src="./diagram.arch" style="width: 100%; height: 480px"></archlang-viewer>The viewer build is a single static SPA — drop dist/ from @archlang/viewer into an S3 bucket or behind nginx and you’re done. No server-side rendering, no SSR backend.
Sizing and responsive layout
Section titled “Sizing and responsive layout”The element renders the iframe at 100% width and 100% height of itself. Size the element however you would size any other block-level element:
<archlang-viewer style="width: 100%; height: 60vh"></archlang-viewer><archlang-viewer class="my-diagram-grid-cell"></archlang-viewer>.my-diagram-grid-cell { width: 100%; aspect-ratio: 16 / 9; }The viewer’s renderer reflows on host resize. Inside narrow layouts the LOD rules favor compact zoom; inside wide layouts everything spreads.
Theming
Section titled “Theming”The viewer’s default theme matches the hosted demo. Custom theming for widgets that ship inside your package works through arch-* CSS utilities (Chapter 20) — the same UnoCSS utilities run in the embedded viewer because both surfaces consume the same compiled CSS.
Cross-origin theming of the embed itself (overriding --au-* tokens from the host page) requires the host and viewer to share an origin: CSS custom properties don’t inherit across iframe boundaries. Self-host the viewer at the same origin as your host page to give it access to your CSS.
When NOT to embed
Section titled “When NOT to embed”- For a
README.mdon GitHub — GitHub strips<script>tags. The embed needs JavaScript to render. - For a slide deck — same issue. The diagram needs a live browser.
- For email — no JavaScript runtime.
For surfaces that can’t run the embed, you have two options. Render the diagram in a build step and commit the image alongside the .arch source — the hosted viewer’s screenshot endpoint and headless renders out of @archlang/render are the practical paths. Or, when you want the whole interactive viewer and not a picture, export a self-contained file:
archlang export html . -o architecture.htmlOne HTML file with the shell, the model and the fonts inlined; it opens offline with no deployed viewer (Chapter 21). Use it to mail an architecture to someone, attach it to a review, or ship it in a release. It is a snapshot, so it does not update when the model does — that is exactly what the embed is for.
The embed is for live, interactive surfaces: dashboards, internal portals, ADR pages with feedback wired through events.
Summary
Section titled “Summary”- One
<script>tag + one<archlang-viewer>element renders a diagram in any page. srcfor a single fetched file; inline<script type="text/arch" data-path="...">children for multi-file workspaces.wrap-manifestcontrols automatic manifest injection.hostoverrides the iframe origin; default is the same origin that served the script.arch-ready,arch-loaded,arch-errorevents integrate the viewer with host-page telemetry.- Widget styling reuses the
arch-*CSS utilities from Chapter 20.
What’s next
Section titled “What’s next”Chapter 24: Library APIs → — the underlying packages (@archlang/engine, @archlang/lsp, @archlang/render) for tool authors.