Skip to content

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

AttributeDefaultEffect
src(none)URL to fetch a single .arch file. Loaded as /main.arch in the workspace.
hostOrigin that served archlang-viewer.jsBase 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:

EventdetailWhen
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.md on 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.
  • src for a single fetched file; inline <script type="text/arch" data-path="..."> children for multi-file workspaces.
  • wrap-manifest controls automatic manifest injection.
  • host overrides the iframe origin; default is the same origin that served the script.
  • arch-ready, arch-loaded, arch-error events 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.