Skip to content
MermaidKit: We Got Tired of Shipping a Browser to Draw a Flowchart

MermaidKit: We Got Tired of Shipping a Browser to Draw a Flowchart

← All Writing
Products mentioned in this post
MermaidKit — Native Mermaid renderer in pure Swift: no JavaScript, no WebView, zero dependencies. All 30 diagram types on six platforms with byte-identical output.

Mermaid lets you write a diagram as plain text, like a flowchart or a sequence chart, and have it drawn for you. Showing one of those diagrams in a native app usually means embedding mermaid.js inside a WKWebView, Apple’s built-in browser view. Every diagram then costs you a whole JavaScript engine, a wait while it draws, text that doesn’t match the rest of your app, and a web process sitting in your memory the whole time.

It’s a lot of machinery to draw a box with an arrow pointing at another box.

So we built MermaidKit: a native Mermaid renderer written in pure Swift. And no, this doesn’t mean it only works on MacOS or iOS. We’ve got it running on basically every modern platform (and a few not-so-modern ones too).

No JavaScript, no WebView, zero dependencies. You hand it Mermaid source and it hands back a drawn diagram synchronously. Parsing and drawing together usually finish in under 15 milliseconds on Apple silicon. The densest diagrams take about 25. It caches its results, so the second time is free. In SwiftUI, it’s a single view:

MermaidView("""
flowchart TD
    A[Start] --> B{Choice}
    B -->|yes| C[Do it]
    B -->|no| D[Skip]
""")

It follows your light or dark mode, sizes itself to the diagram, and if it hits syntax it can’t parse, it falls back to readable monospaced text instead of showing a blank rectangle.

All 30 types, on six renderer targets

MermaidKit parses and lays out all 30 Mermaid diagram types: flowcharts, sequence diagrams, Gantt charts, ER diagrams, state machines, Sankey flow diagrams, and more. Every one of them is in our gallery, rendered by MermaidKit itself, in both light and dark.

The trick is to separate layout from drawing

The interesting part showed up when we tried to make the same renderer work somewhere other than Apple platforms. The useful trick turned out to be separating “where does everything go?” from “how does this platform draw it?” Swift does the first part once. The result is a small JSON scene description we call SceneWire. Then CoreGraphics, Cairo, Skia, or standard web graphics does the painting.1

  flowchart LR
    A["Mermaid / DOT / SQL / git log"] --> B["Swift parser + layout"]
    B --> C["SceneWire (JSON)"]
    C --> D["Native renderer"]

An Android app doesn’t need a Swift toolchain in the app. The Swift core ships as a native library, so Android code just passes in Mermaid text and gets back a finished diagram themed to Material Design:

MermaidDiagram("flowchart LR\n A[Start] --> B[End]", Modifier.fillMaxWidth())

That Swift core is deterministic: the same diagram always produces the same result. So our automated tests can prove the output is identical down to the byte across the five platforms where the Swift core compiles (Android, Windows, WebAssembly, Linux, and macOS). The sixth renderer target is Flutter, which uses the same SceneWire output through Dart. Same source, same bytes, whether it drew on a Mac or inside a Windows .NET app. There’s even a raw-pixels path for screens with no windowing system at all. We have a demo that paints an endless, pannable diagram canvas straight onto a Raspberry Pi’s framebuffer (the raw screen memory, with no window manager in sight). This was not remotely necessary, but once we realized we could do it, we had to.

Beyond Mermaid

At some point we realized Mermaid wasn’t actually special anymore. The renderer only sees SceneWire, so anything we can turn into that same representation gets the rest of the system for free. That led directly to four more inputs:

  • Graphviz DOT goes in, and MermaidKit can hand it back out, so it doubles as a two-way converter between Mermaid and DOT, two common text formats for graphs.
  • Dippin (our own small language for describing AI pipelines, written in .dip files) draws its eight kinds of node as flowchart shapes.
  • SQL DDL: feed in the CREATE TABLE statements that define a database and you get an ER diagram back, with primary, foreign, and unique key badges and crow’s-foot lines showing how the tables relate.
  • Raw git log output becomes a git graph, with branch lanes drawn from the labels Git attaches to each commit. It renders straight in your terminal.

Limitations

MermaidKit is not a drop-in reimplementation of mermaid.js. If you need every styling directive or pixel-for-pixel compatibility, use mermaid.js. MermaidKit handles the core syntax of each type (the constructs in the mermaid.js docs’ main examples), plus a long list of everyday extras. The README has a support table that spells out exactly what’s covered. Styling directives like classDef don’t cause errors; the parser just skips them, because MermaidKit handles theming itself. And if you need support for older iOS versions, we’ll point you to alternatives.

Cross-platform rendering also gave us a testing problem: “looks right to me” isn’t a useful assertion. So every change runs through a geometric linter (an automated checker that looks at the finished shapes). It verifies that lines don’t cut through boxes, that labels don’t get clipped, and that the layout stays stable from one run to the next. When a diagram renders wrong, that’s a bug you can report with a template, and we fix it.

MermaidKit is open source under the permissive MIT license, and it’s on GitHub. If you’ve been shipping a whole browser just to draw boxes and arrows, you can stop now.


  1. For the curious: Apple platforms draw with CoreGraphics and CoreText; Linux uses Silica, a Swift binding for the Cairo graphics library; Android paints with Kotlin’s Canvas; Windows and .NET use SkiaSharp; Flutter uses a Dart CustomPainter; and WebAssembly renders to SVG or Canvas2D. The data moves between languages over the C ABI (the shared calling convention that lets a Kotlin, C#, or Dart program hand a string to Swift and get pixels back), using each language’s standard bridge for that job: JNI on Android, P/Invoke on Windows, and dart:ffi on Flutter. ↩︎

More Posts

20 pages · hugo 0.148.2 · d8f03ca · built Sep 3 18:34
2389 Radio
2389 RADIO Select a station