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.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.