Mermaid diagrams

You can turn Mermaid diagram syntax into native, editable tldraw shapes with the @tldraw/mermaid package. Instead of rendering a static SVG, it parses Mermaid text and creates real geo shapes, arrows, and groups on the canvas. Users can move, resize, restyle, and connect them like any other shape.

Quick start

Install the package alongside tldraw:

npm install @tldraw/mermaid

Then call createMermaidDiagram with an Editor and a Mermaid source string:

import { createMermaidDiagram } from '@tldraw/mermaid'

await createMermaidDiagram(
	editor,
	`
  flowchart TD
    A[Start] --> B{Decision}
    B -->|Yes| C[Do something]
    B -->|No| D[Do something else]
`
)

By default the diagram is centered on the viewport, or on the pointer when the user's paste-at-cursor preference is on. Pass a blueprintRender.position to place it at a specific page point, and set centerOnPosition: false to anchor its top-left corner instead of its center. The finished diagram is a single group. Pass mermaidConfig to override Mermaid's layout settings, such as node spacing or font size.

Supported diagram types

Diagram typeMermaid keywordWhat you get
Flowchartflowchart, graphGeo shapes, arrows, subgraph containers
Sequence diagramsequenceDiagramActor shapes, lifelines, signal arrows, fragment containers
State diagramstateDiagram, stateDiagram-v2State shapes, transitions, compound state containers, fork/join, choice
MindmapmindmapColored geo shapes, parent-child edges, tree hierarchy

Subgraphs, fragments, and compound states become geo rectangles with their child shapes parented to them.

For unsupported types such as pie, gantt, class, and ER, pass an onUnsupportedDiagram callback. Here it falls back to importing Mermaid's rendered SVG:

await createMermaidDiagram(editor, text, {
	async onUnsupportedDiagram(svgString) {
		await editor.putExternalContent({ type: 'svg-text', text: svgString })
	},
})

Without a callback, createMermaidDiagram throws a MermaidDiagramError for unsupported types and parse failures.

Handling pasted Mermaid text

The most common integration is converting Mermaid text when users paste it onto the canvas. Register an external content handler that sniffs for a Mermaid keyword and dynamically imports the package:

import { useEffect } from 'react'
import { defaultHandleExternalTextContent, useEditor } from 'tldraw'

const MERMAID_KEYWORD =
	/^\s*(flowchart|graph|sequenceDiagram|stateDiagram|classDiagram|erDiagram|gantt|pie|gitGraph|mindmap)/

export function MermaidPasteHandler() {
	const editor = useEditor()

	useEffect(() => {
		editor.registerExternalContentHandler('text', async (content) => {
			if (!MERMAID_KEYWORD.test(content.text)) {
				await defaultHandleExternalTextContent(editor, content)
				return
			}

			try {
				const { createMermaidDiagram } = await import('@tldraw/mermaid')
				await createMermaidDiagram(editor, content.text, {
					async onUnsupportedDiagram(svgString) {
						await editor.putExternalContent({ type: 'svg-text', text: svgString })
					},
				})
			} catch {
				await defaultHandleExternalTextContent(editor, content)
			}
		})
	}, [editor])

	return null
}

Drop <MermaidPasteHandler /> inside your Tldraw component and pasting Mermaid text will render it as shapes. The Mermaid pasting example extends this with markdown fence stripping and a toast for unsupported diagrams.

The mermaid dependency is ~2 MB. createMermaidDiagram loads it lazily on first call, and the regex above keeps it from being called for ordinary text, so users who never paste Mermaid don't pay the cost.

Customizing node shapes

By default, blueprint nodes are materialized as tldraw geo shapes. To render them as your own custom shape type instead, pass mapNodeToRenderSpec on blueprintRender:

await createMermaidDiagram(editor, text, {
	blueprintRender: {
		mapNodeToRenderSpec(input) {
			if (input.diagramKind === 'flowchart') {
				return {
					variant: 'shape',
					type: 'pipeline-step',
					props: { mermaidNodeId: input.nodeId },
				}
			}
			// Return undefined to keep the package default for this node.
			return undefined
		},
	},
})

The callback receives diagramKind, nodeId, kind, and the full MermaidBlueprintNode, and returns a MermaidBlueprintNodeRenderSpec: either a geo variant or a custom shape type that's registered on the editor. The package sets w, h, fill, color, dash, size, richText, align, and verticalAlign on every node it creates, so a custom shape's props must accept those keys or validation will fail. See createMermaidDiagram and renderBlueprint in the reference for the full API.

Examples

Prev
Driving the editor
Next
LLM documentation