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.
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.
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 { team: Commerce command CreateOrder query GetOrder }
service Inventory { 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.modules and arch.kinds so stdlib kinds resolve. That behavior is controlled by wrap-manifest (below).
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"> name: my.shop use * from arch.modules use * from arch.kinds </script>
<script type="text/arch" data-path="/orders.arch"> service Orders { team: Commerce command CreateOrder } </script>
<script type="text/arch" data-path="/payments.arch"> service Payments { team: Payments command 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
| 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
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
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/web into an S3 bucket or behind nginx and you’re done. No server-side rendering, no SSR backend.
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
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
- 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, 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/web are the two practical paths.
The embed is for live, interactive surfaces: dashboards, internal portals, ADR pages with feedback wired through events.
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
Chapter 24: Library APIs → — the underlying packages (@archlang/parser, @archlang/core, @archlang/lsp) for tool authors.