@liminis/diagrams
v0.1.5
Published
C4 architecture diagrams: parse C4-PlantUML, lay out with dagre, render to SVG
Maintainers
Readme
@liminis/diagrams
C4 architecture diagrams: parse C4-PlantUML macro syntax, lay it out with dagre, render it to SVG — with optional drag-to-position editing.
Extracted from @liminis/editor, where it
renders ```c4 fenced code blocks. Nothing here is bound to that editor.
Documentation: v3rv.com/liminis-diagrams.
Demo
https://v3rv.com/liminis-diagrams/demo/ — edit C4-PlantUML source and see it re-render live, drag nodes to reposition them, toggle dark mode, and switch between a few preset diagrams. The demo keeps dragged positions in memory only, for as long as the tab is open — this package has no persistence of its own (see Recipe 3), and neither does this demo.
Install
npm install @liminis/diagramsreact and react-dom are optional peers. Installing the package gets you
@dagrejs/dagre and nothing else, so @liminis/diagrams/core works in a CLI or CI job
with no React on disk. Install the peers if you use /react or /server — see
Architecture for why the split exists and which entry
point to pick.
Not sure this package does what you're assuming?
Read Limitations before you build against this package. In short: no editing UI, no persistence, element IDs aren't stable across diagrams, no cross-diagram links.
Parse and lay out
import { parseC4, layoutC4Diagram } from '@liminis/diagrams/core';
const { diagram, errors } = parseC4(`
Person(user, "User", "End user")
System_Boundary(app, "My App") {
Container(fe, "Frontend", "React")
ContainerDb(db, "Database", "PostgreSQL", "Stores data")
}
Rel(user, fe, "Uses", "HTTPS")
Rel(fe, db, "Reads/writes", "SQL")
`);
if (!diagram) {
throw new Error(`parse failed: ${JSON.stringify(errors)}`);
}
const layout = layoutC4Diagram(diagram); // nodes, routed edges, width, heightRender to SVG
import { renderC4DiagramToSVG } from '@liminis/diagrams/server';
const { svg, errors } = renderC4DiagramToSVG(source, /* isDarkMode */ false);Render in React, with dragging
C4InteractiveRenderer is controlled: it takes positions in and calls back with new
ones. Persisting them is the host's job.
import { C4InteractiveRenderer } from '@liminis/diagrams/react';
<C4InteractiveRenderer
diagram={diagram}
isDarkMode={false}
isEditMode={true}
manualPositions={positions}
onPositionChange={setPositions}
/>Pass manualPositions to layoutC4Diagram to bypass dagre for the elements you have
positions for. Persisting them is entirely your call — see
Recipe 3 for a worked
example (including how @liminis/editor does it) and why this package itself never
writes them anywhere.
Render on the command line
npx --package=@liminis/diagrams --package=react --package=react-dom -- render-c4 diagram.puml
# diagram.puml -> diagram.svgUseful for pre-rendering diagrams so a plain  is enough for
GitHub (or any markdown renderer) to show them — see
Rendering diagrams on GitHub for the CI recipe, and
Rendering diagrams in Claude Code for getting Claude
to render real diagrams instead of hand-drawing them.
Supported syntax
Person, System, Container, Component and their _Ext / Db / Queue variants,
plus Deployment_Node, Node, and InfrastructureNode variants; boundary macros;
Rel (with directional variants) and BiRel. See
the C4-PlantUML reference for the full macro table and exactly
which directives (@startuml, !include, SHOW_LEGEND(), LAYOUT_*, …) are applied
versus silently stripped.
Documentation
Building a tool on top of this package? the documentation site covers the entry-point boundary, the full DSL reference, the data model, and runnable recipes for headless rendering, embedding the interactive renderer, and position persistence.
Provenance
The commit history predates this repository: it was recovered from
verveguy/liminis (liminis-app/src/editor/app/editor/c4/, later
packages/editor/src/app/editor/c4/) and carries development from 2026-03-18 onward.
git log --follow works across the move.
License
MIT
