Mo Sharif
Back to writing

Mermaid to React Flow: Building an Honest Importer

In this article

A diagram in a README answers one question: what connects to what? An editor has to answer the next one: what can I change without breaking the rest?

That gap is why I added Mermaid import to Codelit.

A Mermaid-to-React-Flow importer

translates supported Mermaid source into a graph model, calculates positions, and renders editable nodes and edges on a React canvas.

I made import converge on Codelit's existing graph model. That is the payoff: a reader can bring a diagram from their docs into the same editor used for generated architecture, instead of starting from a blank canvas.

Mermaid rendering is enough when the goal is to display documentation. Codelit needs nodes with editable properties, selection state, and the same tools used by generated diagrams. Importing into the application graph makes those interactions consistent across input formats.

Mermaid is not inherently unclickable: its flowchart documentation supports links and callbacks. The distinction is an editable node-based interface, not interactivity versus no interactivity.

That is also why the React Flow canvas sits downstream of import. The editor should not need a separate implementation for every way a diagram arrives.

Codelit's current importer recognizes basic graph and flowchart input, collects nodes by ID, and extracts supported edges. It maps familiar shapes and labels into component types. It does not implement the full Mermaid grammar, so a successful parse is not evidence of complete fidelity.

This is a useful starting input:

Mermaid
graph TD
    A[Client] --> B[API Gateway]
    B --> C[Auth Service]
    B --> D[User Service]
    C --> E[(Database)]
    D --> E

There are five nodes here, not six. Both services refer to the same database, E. Losing that identity changes the architecture even if the picture still looks plausible.

Input featureWhat the importer must decideBoundary to make visible
Node declarationsID, label, and a component-type guessA shape does not prove a service's role
Repeated node IDsOne node with multiple referencesDo not silently split a shared component
Arrows and labelsEndpoints and relationship metadataUnsupported arrow semantics may be lost
SubgraphsMembership, nesting, and cross-group edgesBasic flowchart support is not group support
Styles and classesWhich presentation properties can surviveCurrent parsing skips styling directives
Other diagram familiesWhether a separate grammar is neededSequence and class diagrams are not flowcharts

The implementation stays small by collecting nodes in a map and passing a common architecture object downstream. That shared shape also powers repository analysis: different inputs, one editing workflow.

Parsing should produce a graph with stable IDs and valid connections before layout assigns any coordinates. Keeping those stages separate makes failures explainable: an absent edge is a parsing issue, while two overlapping nodes are a geometry issue.

A fuller importer has five responsibilities:

  1. Recognize the diagram family and supported syntax.
  2. Build a canonical map of nodes and edges.
  3. Validate references and report anything omitted.
  4. Translate the graph into the editor's data model.
  5. Calculate positions and render the result.

These are architectural boundaries, not a claim that the current lightweight parser is a complete compiler. Splitting them clearly is how I would extend it without making every new syntax case affect rendering.

Shape mapping provides a starting guess, not a reliable classifier. A cylinder often represents storage, but a rectangle could be a client, a service, or an external dependency. Keep the original label and let the user correct the inferred role.

The graph contract matters more than a clever heuristic. Codelit's prompt-to-diagram pipeline and the Mermaid importer should converge on a model the editor can validate, regardless of where the initial guess came from.

A round-trip test should compare graph meaning, not screenshot similarity. Import a supported document, export it, import the export, and compare the node IDs, labels, and connections that the format promises to preserve. Layout coordinates can change without changing connectivity.

Test one shared database, a disconnected node, quoted labels, multiple incoming edges, and unsupported syntax. Each case should either preserve the declared meaning or explain the limitation. Quietly drawing half a graph is the failure to avoid.

The boundary extends to infrastructure exports. A diagram can produce a useful starter file; it cannot prove that the resulting infrastructure is safe to deploy.

A hand-written parser is reasonable for a deliberately small syntax subset. As the supported grammar grows, a parser library or a maintained upstream parser becomes more attractive. The decision should follow the compatibility contract, not the appeal of owning a compiler.

I would spend the next hour on fixtures before spending it on another diagram family. One small importer that preserves meaning is worth more than broad support that quietly drops an edge.

Try a basic flowchart in Codelit, then check the connections against your source. That comparison is the real acceptance test.

Questions people actually ask

Can you turn a Mermaid diagram into a React Flow canvas?
Yes. Parse supported Mermaid source into nodes and edges, map those records to the application's graph model, and compute positions for the editor. Mermaid's SVG renderer can support links and callbacks, but it does not provide the draggable React editor that Codelit needs.
Which Mermaid diagram types does Codelit import?
The importer reviewed for this article handles a subset of graph and flowchart syntax: basic node declarations, recognized shapes, and several arrow forms. It is not a full Mermaid implementation. Sequence diagrams, class diagrams, nested groups, and styling should not be assumed to survive an import.
Should imported node IDs be prefixed by subgraph?
Not automatically. A repeated Mermaid node ID refers to the same node, even when it is mentioned in different groups. An importer must preserve that identity. If the target editor needs internal IDs, keep one consistent mapping and rewrite every connected edge through it.
Why does a diagram importer need its own layout step?
The source describes connections, not the coordinates expected by the editor. React Flow renders the positions it receives. A layout library can calculate those positions from the graph, but the importer still owns dimensions, direction, grouping, and the conversion between coordinate conventions.
Can Mermaid import and export be lossless?
Only for syntax and properties represented by both sides. A basic graph can preserve node identity and connectivity, while classes, annotations, styling, or unsupported diagram families may be lost. Test round trips against a declared supported subset, rather than promising that every Mermaid document will survive unchanged.