An offline PlantUML editor for the desktop. Bundled JRE. No accounts. No Java install dance.
Download Β· Docs Β· Changelog Β· Report bug
PlantUML is the right way to draw diagrams β version-controlled, plain text, automatable. The web editors force you online, the JVM setup is friction, and the existing desktop tools feel like relics from 2008. Open UML is a desktop app that runs entirely offline, bundles its own JRE, and feels like a modern IDE.
| Offline | Zero network calls except a one-shot update check against GitHub. No telemetry, no analytics, no upload. |
| Live preview | Diagrams render as you type (300 ms debounce). Multi-diagram files show tabs. |
| Professional editor | Monaco with PlantUML syntax highlighting, autocomplete snippets, find/replace, multi-cursor, bracket pair colorization. |
| Export anywhere | PNG, SVG, PDF β or copy the rendered diagram straight to your clipboard. |
| Themed | Cyan-teal accent on either a deep slate (dark) or warm paper (light) surface. |
| Free, MIT | Use it, fork it, ship it. |
Open UML is a standard Electron app β a Chromium renderer driving a Node.js main process, communicating over a typed IPC bridge. The interesting bit is that almost all the design work happens at the boundary: PlantUML rendering is delegated to a bundled JRE subprocess; the rest is plain React.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Electron (Chromium + Node) β
β β
β ββββββββββββββββββββββββ IPC ββββββββββββββββββββββββββββ β
β β Renderer β ββββββββββββββββββΊ β Main Process β β
β β (React + Monaco) β electronAPI β (Node.js) β β
β β β β β β
β β App.tsx ββββββββββββββ state + handlers ββΊβ app/main/index.ts β β
β β ββ TopBar β β ββ createWindow() β β
β β ββ Editor (Monaco) β β ββ IPC handlers β β
β β ββ Preview (SVG) β β ββ electron-updater β β
β β ββ StatusBar β β ββ application menu β β
β β ββ CommandPalette β β ββ single-instance lockβ β
β β ββ ErrorPanel β β β β
β β ββ ErrorBoundary β β app/main/plantuml-parser β β
β β β β ββ splitDiagrams() β β
β β lib/ β β ββ parsePlantumlError() β β
β β ββ constants.ts β β ββ looksLikeErrorImage()β β
β β ββ cn.ts (className)β β β β
β β ββ monaco-plantuml.tsβ β preload.ts β β
β β ββ hooks/ β β ββ contextBridge β β β
β β ββ useLocalStorageβ β window.electronAPI β β
β β ββ useEditorSettingsβ β β β
β ββββββββββββββββββββββββ ββββββββββββββββββββββββββββ β
β β β
β β spawn() β
β βΌ β
β ββββββββββββββββββββββββββββ β
β β bundled JRE + PlantUML β β
β β plantuml.jar β β
β β plantuml-pdf.jar β β
β β (live rendering, β β
β β fully offline) β β
β ββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- User types into Monaco (debounced 300 ms).
App.tsxcallswindow.electronAPI.renderDiagram({ source, format }).- Main process splits the source on
@startXXXβ¦@endXXXblocks (splitDiagrams). PlantUML's-pipemode renders only the first block β splitting lets us render every block in parallel. - For each block, main process spawns the bundled JRE:
java -cp <plantuml.jar;plantuml-pdf.jar> net.sourceforge.plantuml.Run -tsvg -pipe - Output is a base64 data URL (
data:image/svg+xml;base64,β¦) returned to the renderer. - Renderer decodes the data URL and inlines the SVG (text becomes selectable, copyable, themable).
All renderer β main communication goes through the typed window.electronAPI exposed by preload.ts. Twelve methods, no globals, no nodeIntegration. Security defaults: contextIsolation: true, nodeIntegration: false, sandbox-friendly preload.
| Method | Direction | Purpose |
|---|---|---|
renderDiagram({ source, format }) |
renderer β main | Render PlantUML source. Returns base64 SVG/PNG data URLs. |
exportDiagram({ data, format }) |
renderer β main | Save the current preview to disk via showSaveDialog. |
exportPdf(source, defaultPath) |
renderer β main | Render to PDF via the bundled plantuml-pdf companion jar. |
copyImage(data, format) |
renderer β main | PNG to clipboard as a real image; SVG as vector text. |
openFile() / saveFile() / saveAsFile() |
renderer β main | Standard file dialogs. |
readFileByPath(path) |
renderer β main | Drag-and-drop open (no dialog). |
checkUpdates() |
renderer β main | GitHub Releases check; never auto-installs. |
onMenuAction(cb) / onRecentFileOpened(cb) / onOsOpenedFile(cb) |
main β renderer | Push native menu actions and OS-opened files. |
openExternal(url) |
renderer β main | Open URL in default browser. |
A deliberate palette for an engineering drawing tool. Not the SaaS-AI default.
| Token | Light | Dark | Role |
|---|---|---|---|
--bg |
#fafaf9 |
#0a1218 |
Page surface |
--bg-elevated |
#ffffff |
#0f1721 |
Cards, app panels |
--surface-1 / -2 / -3 |
#f4f4f3 / #ebebe9 / #dcdcd8 |
#131c26 / #1a2632 / #2a3645 |
Hover, dividers, selected |
--fg |
#1a1a1a |
#f0fdfa |
Primary text |
--fg-muted |
#57534e |
#94a3b8 |
Secondary text |
--accent |
#0f766e (teal-700) |
#22d3ee (cyan-400) |
Action, focus, link |
--warn |
#b45309 |
#fbbf24 |
Warnings, errors |
--danger |
#b91c1c |
#f87171 |
Destructive actions |
Single accent per composition. No gradients on chrome. No shadows on text. No glass effects.
| Use | Family | Weight |
|---|---|---|
| UI, body, headings | IBM Plex Sans | 400 / 500 / 600 |
| Code, editor | IBM Plex Mono | 400 / 500 |
| Display wordmark | IBM Plex Sans SemiBold (traced) | 600 |
Self-hosted via @fontsource/* packages β the editor opens without a network.
- Single accent per composition. Borders are 1px in the border token β not the SaaS "soft grey shadow under every card" kit.
- One bold element. A subtle dot-grid background fills the preview pane (engineering graph paper). Nowhere else.
- Generous negative space. When in doubt, remove.
Seven primitives under app/renderer/components/ui/: Button, IconButton, Toggle, Tooltip, Badge, Separator, Select. Every other component is composed from these.
| Shell | Electron 28 |
| UI | React 18 + TypeScript 5 |
| Build | Vite 5 + vite-plugin-electron |
| Styles | Tailwind CSS 3 (semantic tokens, no color ramp leakage) |
| Editor | Monaco (@monaco-editor/react) with a custom PlantUML language definition |
| Icons | lucide-react |
| Rendering engine | PlantUML (bundled JAR + plantuml-pdf companion) |
| Runtime | Bundled OpenJDK JRE β Windows JRE on Windows builds, macOS JRE on macOS builds |
| Tests | Vitest 2 |
| Lint | ESLint 8 + @typescript-eslint |
| Packaging | electron-builder 24 |
open-uml/
βββ app/
β βββ main/ # Electron main process
β β βββ index.ts # Window, IPC, menu, auto-update
β β βββ plantuml-parser.ts # Pure functions (testable)
β β βββ plantuml-parser.test.ts # 21 Vitest cases
β βββ preload.ts # contextBridge β window.electronAPI
β βββ renderer/ # React UI
β β βββ App.tsx
β β βββ components/
β β β βββ Editor.tsx
β β β βββ Preview.tsx
β β β βββ TopBar.tsx
β β β βββ StatusBar.tsx
β β β βββ CommandPalette.tsx
β β β βββ ErrorPanel.tsx
β β β βββ ErrorBoundary.tsx
β β β βββ ui/ # Primitives
β β βββ lib/
β β β βββ constants.ts # Magic numbers + storage keys
β β β βββ cn.ts # clsx + tailwind-merge
β β β βββ monaco-plantuml.ts # Language + themes + snippets
β β βββ hooks/
β β βββ useLocalStorage.ts
β β βββ useEditorSettings.ts
β βββ assets/ # Logos, app icons (auto-regenerated)
β βββ plantuml/ # JAR + JRE (platform-scoped at build)
βββ docs/landing/ # Static landing page (Vercel/Netlify)
βββ logo-family/ # Brand masters (SVG + PNG + font)
βββ scripts/
β βββ regenerate-icons.py # Pillow-only icon regenerator
β βββ setup-plantuml.md
βββ public/
β βββ favicon.svg
βββ .claude/launch.json # Browser preview server
βββ preview.cjs # Tiny static server (dev only)
βββ .github/workflows/release.yml
βββ CHANGELOG.md
βββ DEPLOY.md # Launch checklist (certs, notarization)
βββ LICENSE
βββ README.md
βββ electron-builder.yml
βββ tailwind.config.js
βββ postcss.config.js
βββ vite.config.ts
βββ vitest.config.ts
βββ package.json
Download the latest installer for your platform from the Releases page.
| Platform | File | Notes |
|---|---|---|
| Windows | OpenUML-Setup-X.Y.Z.exe |
NSIS installer. No SmartScreen warning once code-signed (see DEPLOY.md). |
| macOS | OpenUML-X.Y.Z.dmg |
Drag to Applications. First launch: right-click β Open if Gatekeeper blocks. |
- Launch Open UML.
- Type PlantUML in the left pane β the preview renders as you type.
- Save with
Ctrl+S/Cmd+S. The file extension.pumlis preferred but.plantuml,.iuml,.pu, and.txtall work. - Export via
Ctrl+Shift+G(SVG) /Ctrl+Shift+P(PNG) /Ctrl+Shift+?(PDF).
| Shortcut | Action |
|---|---|
Ctrl+N / Cmd+N |
New |
Ctrl+O / Cmd+O |
Open |
Ctrl+S / Cmd+S |
Save |
Ctrl+Shift+S / Cmd+Shift+S |
Save as |
Ctrl+Shift+R / Cmd+Shift+R |
Render |
Ctrl+Shift+G / Cmd+Shift+G |
Export SVG |
Ctrl+Shift+P / Cmd+Shift+P |
Export PNG |
Ctrl+Shift+C / Cmd+Shift+C |
Copy image |
Ctrl+Shift+K / Cmd+Shift+K |
Command palette |
Ctrl+Scroll |
Zoom preview |
Space + drag |
Pan preview |
- Node.js 20+
- npm
- Git
git clone https://github.com/choksi2212/open-uml.git
cd open-uml
npm installThe JRE + PlantUML jars are committed at app/plantuml/ so there's nothing to fetch.
| Command | Description |
|---|---|
npm run dev |
Vite dev server only |
npm run electron:dev |
Vite + Electron together |
npm run build |
Production build (renderer + main + preload) |
npm test |
Run Vitest |
npm run test:watch |
Vitest in watch mode |
npm run test:coverage |
Vitest with coverage report |
npm run lint |
ESLint |
npm run dist |
Build installers via electron-builder |
npm run release |
Build + create GitHub release |
The app/assets/ icons are derived from logo-family/ masters. After any logo update:
python scripts/regenerate-icons.pyOutputs (via Pillow β no ImageMagick needed):
app/assets/icon.icoβ Windows multi-size (16/24/32/48/64/128/256)app/assets/icon.icnsβ macOS (1024Γ1024)app/assets/_lockup-light.svg/_lockup-dark.svgβ TopBar lockuppublic/favicon.svgβ Browser faviconapp/assets/social-avatar.pngβ GitHub / Twitter avatar
npm run distOutputs land in build/:
build/OpenUML-Setup-2.0.0.exeβ Windows NSISbuild/OpenUML-2.0.0.dmgβ macOS
Each platform packages only its own JRE. The macOS CI job mounts the produced DMG and asserts that bin/java is a Mach-O binary β that's the regression guard for the v1.0.1 bug where Windows JRE ended up in the macOS DMG.
See DEPLOY.md for the actual launch checklist. The short version: buy a Windows EV cert and an Apple Developer account, set the GitHub secrets, replace identity: null in electron-builder.yml with your real signing identity. Without these, users will see SmartScreen on Windows and the right-click β Open flow on macOS.
Packaged builds use electron-updater against the GitHub Releases feed. A new tag on main triggers the release workflow, which produces installers, attaches them to a GitHub release, and clients auto-download + prompt to install on quit. Unsigned builds fall back to a manual Help β Check for Updates flow.
Pull requests welcome. Conventional commits preferred (feat:, fix:, docs:, refactor:, chore:, test:).
For larger changes, please open an issue first to discuss the approach. Test additions go alongside the change they cover β splitDiagrams, parsePlantumlError, and looksLikeErrorImage are the obvious places for new parser logic.
- PlantUML β the rendering engine that makes this possible.
- Electron, React, Vite, Tailwind CSS, Monaco Editor, Lucide β the foundation.
- IBM Plex β the typography that gives the app its voice.
MIT Β© Open UML contributors.

