DEV Community

Cover image for Stop Screenshotting Architecture Diagrams: Build Them as Single-File HTML
Nasrul Hazim
Nasrul Hazim

Posted on

Stop Screenshotting Architecture Diagrams: Build Them as Single-File HTML

TL;DR

  • Spent today rebuilding two training diagrams — a layered reference architecture and a workflow swimlane — as single-file HTML instead of exported images.
  • The trick: data in a plain JS array, layout in CSS Grid, connectors in an SVG overlay measured at runtime.
  • Result: diagrams you can step through, click into, and hand over as one file that opens offline.

Why not just export a PNG?

Because a screenshot is stale by the next sprint, and nobody re-opens the drawing tool to fix it.

Approach Editable by a dev Interactive Portable
PNG from a drawing tool No (need the source file) No Yes
Mermaid in a doc Yes No Needs a renderer
Hand-built HTML Yes (it's a data array) Yes One file, opens anywhere

The third option costs a few hours once. After that, updating a component is editing one object in an array.

Separate the data from the drawing

This is the whole design. Nothing about position, colour, or DOM lives in the content:

const TIERS = [
  { key: 'clients', label: 'Clients', sub: 'consumers · admins' },
  { key: 'edge',    label: 'Edge',    sub: 'TLS · load balancing' },
  // ...
];

const COMPONENTS = [
  { id: 'app', tier: 'app', x: .34, label: 'Control Plane', tech: 'PHP-FPM',
    role: 'Governance UI and sync orchestration.',
    points: [{ type: 'info', text: 'Pushes approved config downstream' }] },
  // ...
];

const LINKS = [['consumers', 'edge'], ['edge', 'gateway'], /* ... */];
Enter fullscreen mode Exit fullscreen mode

tier picks the row. x is a fraction (0..1) across that row, not a pixel. So the layout survives any viewport without me hand-tuning coordinates.

Layout: Grid for the boxes, SVG on top for the wires

  +--------------------------------------------------+
  | lane label |  [card 1]   [card 2]   [card 3]      |  <- CSS Grid row
  |------------+-------------------------------------|
  | lane label |       [card 4]      [card 5]         |
  +--------------------------------------------------+
        ^                    ^
   sticky column      SVG overlay (position:absolute; inset:0)
                      draws paths between measured centres
Enter fullscreen mode Exit fullscreen mode

CSS Grid does the honest work:

grid.style.gridTemplateColumns = `186px repeat(${steps.length}, 178px)`;
grid.style.gridTemplateRows = `repeat(${LANES.length}, 1fr)`;
Enter fullscreen mode Exit fullscreen mode

Then measure the rendered cards and draw connectors into the overlay:

function centre(el) {
  const r = el.getBoundingClientRect(), base = board.getBoundingClientRect();
  return { x: r.left - base.left + r.width / 2, y: r.top - base.top + r.height / 2 };
}
Enter fullscreen mode Exit fullscreen mode

Measure after paint (requestAnimationFrame), and re-measure on resize or when a side drawer opens — otherwise your wires point at where the boxes used to be. That was the one bug that cost me real time.

The bit learners actually respond to

A dot that travels along the flow as you step through it. No library, ~10 lines:

function moveToken(from, to) {
  const a = centres[from], b = centres[to], t0 = performance.now(), dur = 650;
  (function tick(now) {
    const p = Math.min((now - t0) / dur, 1);
    const e = p < .5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2; // easeInOutQuad
    token.style.left = (a.x + (b.x - a.x) * e - 6) + 'px';
    token.style.top  = (a.y + (b.y - a.y) * e - 6) + 'px';
    if (p < 1) requestAnimationFrame(tick);
  })(performance.now());
}
Enter fullscreen mode Exit fullscreen mode

It's cosmetic, and it's the thing that makes a request's journey land in a room. Movement encodes causality better than an arrow does.

Trade-offs, honestly

Win Cost
One file, no build step, opens offline You hand-write the layout logic
Content is a data array anyone can edit No auto-layout — you place things
Full control of interaction Accessibility is on you (add keyboard nav)

If your diagram is throwaway, use Mermaid. If it's going to be taught from repeatedly and evolve with the system, the data-driven HTML pays for itself fast.

Next

Extracting the shell into a reusable template so a new diagram is just a new data file. That's the real deliverable — not the diagram, the mould it came out of.

Top comments (0)