nomnoml reads text and hands the layout to graphre
The sassy UML diagram renderer
At a glance
- What is it?
- A compact text syntax for UML diagrams that renders SVG in Node, a canvas in the browser and SVG from the CLI, shipped as dist bundles with no sources and a twenty month gap between the newest release and the last commit.
- Who is it for?
- The renderer fits teams that keep a diagram as text next to the code it describes, and does not fit anyone who needs the picture itself as the deliverable, because the documented outputs are SVG and a browser canvas with no raster path and no documented export from the web app.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 47 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 4, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two render targets, and the CLI wires up only one
The library exposes two drawing calls aimed at different hosts. `nomnoml.renderSvg(src)` is the one headed for NodeJS, and the command line interface is described as exposing the SVG renderer, so `npx nomnoml` produces SVG and nothing else is claimed for it. The canvas path exists for the browser:
<script src="//unpkg.com/graphre/dist/graphre.js"></script>
<script src="//unpkg.com/nomnoml/dist/nomnoml.js"></script>
<canvas id="target-canvas"></canvas>
<script>
var canvas = document.getElementById('target-canvas')
var source = '[nomnoml] is -> [awesome]'
nomnoml.draw(canvas, source)
</script>Both unpkg addresses carry no version and no integrity attribute, and both are protocol relative, so a page that copies this block resolves whatever those dist paths hold at load time. Order matters: graphre.js is asked for first, since graphre is the only dependency the manifest declares. Nothing else is needed on the page, no bundler and no build step.
The published package is dist bundles and an editor mode
The manifest is at version 1.7.0 and points `main` at `dist/nomnoml.js`. Its `files` array has twelve entries: index.d.ts, three dist bundles (nomnoml.js, nomnoml-cli.js, webapp.js), index.html, nomnoml.css, favicon.png, img/interaction-tutorial.svg, and four files from codemirror/. No src directory is published, so the tarball gives you JavaScript and no TypeScript sources. `types` points at index.d.ts, a single file kept at the repository root, and no script in the manifest emits declarations: `checktypes` runs `tsc --noEmit` against src and against webapp, and the two rollup steps only bundle. That array also publishes CodeMirror assets while neither `dependencies` nor `devDependencies` names a CodeMirror package, so the editor itself has to arrive from the host page. React, react-dom and file-saver sit in devDependencies, which is how the web app ships as one file.
npm run build runs the test suite twice
The build script is `npm run lib && npm run webapp && npm run test`, and `lib` is `rollup --bundleConfigAsCjs -c src/rollup.config.js && npm test`. Since test is `node --test && npm run test_cli`, one build runs the node test runner and the CLI fixture twice. The CLI fixture is the most informative line in the manifest:
rm -f test/output.svg && node dist/nomnoml-cli.js test/import-test.nomnoml test/output.svgThat is the only place two positional arguments are visible: an input file and an output path, with the previous output deleted first so a failed run cannot pass on a stale file. The README shows only `npx nomnoml input-file.noml`, which leaves stdout versus file unresolved. Note also two extension spellings for the same job: .noml in that CLI sample, .nomnoml in the import directive example and in the fixture.
prepare bundles the web app on a checkout install
`prepare` is `npm run webapp`, and webapp is `rollup --bundleConfigAsCjs -c webapp/rollup.config.js`. npm runs prepare when installing from a git URL, so installing a clone needs the devDependencies (rollup 4.28.0, typescript 5.7.2, rollup-plugin-git-version, rollup-plugin-ignore) in place before the install can finish. Someone who only wants the renderer should take the published package instead, where prepare does not run. The site itself is index.html with nomnoml.css and favicon.png, with vercel.json at the root for hosting, and the web application is a plain editor with a live preview that is purely client side and keeps the diagram in the browser's localStorage, hedged in the source with the line that your diagram should be here next time you visit, but with no guarantees. No export or sync path is described for it.
graphre owns the layout and the ranker is the widest choice
The single runtime dependency is graphre ^0.1.3, so the grammar, the parser and the drawing live in this repository while layout decisions come from the other package. Several directives are really layout controls handed to it: #direction with down or right, then #gutter, #spacing, #padding, #gravity, #edgeMargin, #bendSize and #arrowSize for routing. #ranker accepts network-simplex, tight-tree or longest-path, the widest set any single directive offers, while #acyclicer documents one value, greedy. #edges chooses hard or rounded, #fill takes a semicolon separated pair such as `#fill: #eee8d5; #fdf6e3`, and #background can be transparent. #title has one documented value that is neither a color nor a size: the word filename. Custom classifiers add their own `#.` directive, for instance `#.box: fill=#8f8 dashed`, where the four digit color and the dashed modifier sit in one space separated list.
The newest release note names an attribute the README skips
v1.7.0, published 2024-12-14, is titled "Decorate SVG output with data-compartment attribute". The interactivity section never mentions data-compartment. What it shows is data-name attached to SVG shapes and to the `<g>` containers, and a handler that reads it:
document.querySelector('svg').onclick = function (e) {
console.log(e.target.closest('g[data-name]')?.attributes['data-name'])
}Three details sit in those three lines. The selector grabs the first svg on the page, so a page carrying two diagrams wires up only one of them. The handler has to be attached after the SVG is in the document, because querySelector runs before any insertion happens. And the code logs the Attr object rather than its value, so a caller that wants the name as text has to read `.value` itself. Interaction is a documented possibility of the SVG output rather than behaviour the renderer performs.
The import directive is written for one mode
`#import: my-common-styles.nomnoml` is offered as working in the command line interface mode, where it divides a complex diagram across several files. The library section says nothing about the same directive working for renderSvg or draw, so a page rendering from a string has no documented way to pull in a shared file, and the browser editor cannot resolve a filesystem path at all. The fixture that covers this is test/import-test.nomnoml, reached only through the CLI test. The same syntax treats `//` as a comment marker at the start of a line: `//[commented]` disappears, while `[not //commented]` keeps its slashes because the marker sits inside the node. For two nodes that must share one display name, an id attribute gives each its own handle, as in `[<actor id=a>User]` beside `[<actor id=b>User]`.
Eighteen line styles and a classifier without a partner
The association list gives eighteen spellings over nine names: association in three forms, dependency, generalization, implementation, composition and aggregation in two each, ball and socket in three, then `--` for a note and `-/-` for hidden. The classifier list holds twenty five entries, from the bare `[name]` through abstract, instance, reference, note, package, frame, database, pipe, start, end, state, choice, sync, input, lollipop, sender, socket, receiver, transceiver, actor, usecase, label, hidden and table. `[<input> name]` is in that list and no output entry is. Two entries also break the pattern of the rest, since `[<lollipop> lollipop]` and `[<socket> socket]` show a literal label where every other entry shows the placeholder name. The table classifier carries its own column syntax, `[<table> name| a | 5 || b | 7]`, where a single pipe opens nested content as the Decorator example shows and the doubled pipe appears where the second column begins.
Editorial conclusion
The renderer fits teams that keep a diagram as text next to the code it describes, and does not fit anyone who needs the picture itself as the deliverable, because the documented outputs are SVG and a browser canvas with no raster path and no documented export from the web app. Check three things before committing: whether the layout directives, especially #ranker, produce the arrangement you need; whether the npm version matches the commits you care about, since the newest release is v1.7.0 from 2024-12-14 while the last commit to master is dated 2026-08-19; and whether you need the import directive outside the command line, the only mode it is written for.
Frequently asked questions
What does nomnoml need before it can render a diagram in Node?
Only the package. The manifest declares one runtime dependency, graphre ^0.1.3, and the documented Node path is `npm install nomnoml` followed by `require('nomnoml')` and a call to `nomnoml.renderSvg(src)`.
Can nomnoml draw into a browser canvas without a bundler?
Yes, with two script tags: graphre.js first, then nomnoml.js, then `nomnoml.draw(canvas, source)` on a canvas element. Neither script address carries a version, so a page loads whatever those dist paths point to on unpkg that day.
Which output formats does nomnoml produce?
SVG from Node through renderSvg and SVG from the command line, plus a canvas draw call for the browser. Neither the manifest nor the description of the syntax mentions a raster or print output.
How does the nomnoml web app keep a diagram between visits?
In the browser's localStorage. The web application is described as purely client-side, with the hedge that your diagram should be there next time you visit, but with no guarantees, and no export or sync path is documented.
How many line and node shapes does the nomnoml syntax define?
Eighteen line spellings over nine names, and twenty five classifier entries from the bare `[name]` up to table, usecase and lollipop. Custom classifier styles add whatever `#.` prefixed directives you write.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/skanaar-nomnoml)