Einleitung
Mermaid ist eine Auszeichnungssprache, mit der sich Diagramme deklarativ erstellen lassen, indem eine JavaScript-Bibliothek die Grafiken generiert.
Mermaid wurde von Knut Sveidqvist entwickelt, der sich inspirieren ließ, als er mit seinen Kindern Arielle, die Meerjungfrau (The Little Mermaid) sah. Ursprünglich als Werkzeug zur Diagrammerstellung aus Text mit Markdown-ähnlicher Syntax entwickelt, hat es sich zu einem vollständigen Ökosystem entwickelt, das in der Community weit verbreitet ist: Das Projekt zählt mittlerweile über 87.500 Sterne auf GitHub.
Mermaid lässt sich nativ in Systeme wie GitHub, GitLab, Visual Studio Code oder Notion integrieren, unter anderem. Ebenso gibt es Integrationen für praktisch alle Frontend-Bibliotheken und -Frameworks.
Mit der einfachen Sprache von Mermaid lassen sich Diagramme deklarativ definieren, ohne ein visuelles Zeichenprogramm erlernen zu müssen, was von Natur aus langsamer ist. Außerdem ist es viel einfacher, Code zu pflegen als Gimp- oder El Pato-Quelldateien.
Ich entdeckte es (etwas spät) bei der Erstellung des Lernleitfadens für NLP.
Im Folgenden stelle ich einige Integrationsbeispiele und mit Mermaid erstellte Diagramme vor, gefolgt von einer Anleitung zur Installation und Anpassung in Astro.
Inhaltsverzeichnis
Inhaltsverzeichnis
Beliebte Mermaid-Integrationen
Je nach Stack kann man die offizielle mermaid-Bibliothek oder Community-Wrapper
verwenden:
- Svelte / SvelteKit:
@friendofsvelte/mermaid - React:
mermaid(offiziell, clientseitiges Rendering),mdx-mermaid - Vue.js:
vue-mermaid-string,vue-mermaid-render - Next.js:
mermaid+dynamic import,mdx-mermaid - Angular:
mermaid(offiziell) oder Integration mitngx-markdown+ Mermaid - Flutter / Dart:
mermaid(Dart JS Interop),flutter_smooth_markdown(enthältMermaidDiagram) - Nuxt:
@d0rich/nuxt-content-mermaid - Docusaurus:
@docusaurus/theme-mermaid - VitePress:
vitepress-plugin-mermaid - Astro:
astro-mermaidoder clientseitiges Rendering mitmermaid
Beispiele
Graph TD
Quellcode:
```mermaidgraph TD A[Mermaid-Block in MDX schreiben] --> B[Astro verarbeitet ihn] B --> C[Inline-SVG wird generiert] C --> D[Diagramm wird im Beitrag gerendert]```Gerendertes Diagramm:
graph TD
A[Mermaid-Block in MDX schreiben] --> B[Astro verarbeitet ihn]
B --> C[Inline-SVG wird generiert]
C --> D[Diagramm wird im Beitrag gerendert]
Flowchart LR
Quellcode:
```mermaidflowchart LR Start([Start]) --> Schritt1[Ziel definieren] Schritt1 --> Schritt2[Mermaid-Block schreiben] Schritt2 --> Schritt3[In Astro rendern] Schritt3 --> Ende([Ende])```Gerendertes Diagramm:
flowchart LR
Start([Start]) --> Schritt1[Ziel definieren]
Schritt1 --> Schritt2[Mermaid-Block schreiben]
Schritt2 --> Schritt3[In Astro rendern]
Schritt3 --> Ende([Ende])
Sequence Diagram
Quellcode:
```mermaidsequenceDiagram participant U as Benutzer participant M as MDX participant A as Astro participant R as Renderer U->>M: Mermaid-Block schreiben M->>A: Inhalt kompilieren A->>R: Inline-SVG generieren R-->>U: Diagramm anzeigen```Gerendertes Diagramm:
sequenceDiagram
participant U as Benutzer
participant M as MDX
participant A as Astro
participant R as Renderer
U->>M: Mermaid-Block schreiben
M->>A: Inhalt kompilieren
A->>R: Inline-SVG generieren
R-->>U: Diagramm anzeigen
Mindmap
Quellcode:
```mermaidmindmap root((Grundlagen)) Sprache (langue) Rede (parole) Sprachkompetenz Sprachliche Performanz Sprachzeichen Synchronie und Diachronie```Gerendertes Diagramm:
mindmap
root((Grundlagen))
Sprache (langue)
Rede (parole)
Sprachkompetenz
Sprachliche Performanz
Sprachzeichen
Synchronie und Diachronie
Class Diagram
Quellcode:
```mermaidclassDiagram class Tier { +String name +fressen() } class Hund { +bellen() } Tier <|-- Hund```Gerendertes Diagramm:
classDiagram
class Tier {
+String name
+fressen()
}
class Hund {
+bellen()
}
Tier <|-- Hund
State Diagram
Quellcode:
```mermaidstateDiagram-v2 [*] --> Inaktiv Inaktiv --> Aktiv: starten() Aktiv --> Pausiert: pausieren() Pausiert --> Aktiv: fortsetzen() Aktiv --> [*]: stoppen()```Gerendertes Diagramm:
stateDiagram-v2
[*] --> Inaktiv
Inaktiv --> Aktiv: starten()
Aktiv --> Pausiert: pausieren()
Pausiert --> Aktiv: fortsetzen()
Aktiv --> [*]: stoppen()
Entity Relationship (ER)
Quellcode:
```mermaiderDiagram BENUTZER ||--o{ BESTELLUNG : gibt_auf BESTELLUNG ||--|{ BESTELLPOSITION : enthaelt PRODUKT ||--o{ BESTELLPOSITION : erscheint_in```Gerendertes Diagramm:
erDiagram
BENUTZER ||--o{ BESTELLUNG : gibt_auf
BESTELLUNG ||--|{ BESTELLPOSITION : enthaelt
PRODUKT ||--o{ BESTELLPOSITION : erscheint_in
Gantt
Quellcode:
```mermaidgantt title Veröffentlichungsplan dateFormat YYYY-MM-DD section Inhalt Verfassen :a1, 2026-04-22, 2d Überarbeitung :a2, after a1, 1d section Veröffentlichung Layout :a3, after a2, 1d Veröffentlichen :milestone, m1, after a3, 0d```Gerendertes Diagramm:
gantt
title Veröffentlichungsplan
dateFormat YYYY-MM-DD
section Inhalt
Verfassen :a1, 2026-04-22, 2d
Überarbeitung :a2, after a1, 1d
section Veröffentlichung
Layout :a3, after a2, 1d
Veröffentlichen :milestone, m1, after a3, 0d
Pie Chart
Quellcode:
```mermaidpie title Aufgabenverteilung "Dokumentation" : 35 "Entwicklung" : 45 "Tests" : 20```Gerendertes Diagramm:
pie title Aufgabenverteilung
"Dokumentation" : 35
"Entwicklung" : 45
"Tests" : 20
Git Graph
Quellcode:
```mermaidgitGraph commit id: "init" branch feature checkout feature commit id: "neues-diagramm" checkout main merge feature commit id: "release"```Gerendertes Diagramm:
gitGraph
commit id: "init"
branch feature
checkout feature
commit id: "neues-diagramm"
checkout main
merge feature
commit id: "release"
Weitere Beispiele hier.
Installationsanleitung für Astro
1. Mermaid installieren
bun add mermaid2. Remark-Plugin in astro.config.ts
Das Problem mit Build-Zeit-Rendering-Lösungen (wie astro-mermaid oder
rehype-mermaid) ist, dass sie Konflikte mit astro-expressive-code
erzeugen, der Bibliothek für Syntax-Highlighting, und die Hervorhebung
in allen ```javascript, ```bash etc.-Blöcken kaputt machen.
Die Lösung ist, Mermaid clientseitig zu rendern, aber expressiveCode darf
die ```mermaid-Blöcke nicht verarbeiten. Dafür wandelt ein Remark-Plugin
diese Blöcke in rohen HTML-Code <pre class="mermaid"> um, bevor
expressiveCode sie sieht:
import { visit } from 'unist-util-visit';
function remarkMermaidBypass() { return (tree: any) => { visit(tree, 'code', (node: any, index: number | undefined, parent: any) => { if (node.lang === 'mermaid' && parent && typeof index === 'number') { parent.children[index] = { type: 'html', value: `<pre class="mermaid">\n${node.value}\n</pre>`, }; } }); };}In das Array markdown.remarkPlugins einfügen (Reihenfolge wichtig: muss zuerst stehen):
markdown: { remarkPlugins: [ remarkMermaidBypass, // zuerst remarkToc, remarkMath, remarkCollapse, ], // ...},Wichtig ist auch, dass die MDX-Integration die Markdown-Konfiguration erbt, anstatt eigene Plugins zu definieren:
mdx({ extendMarkdownConfig: true, // erbt remarkPlugins und rehypePlugins}),Werden rehypePlugins oder remarkPlugins direkt an mdx() übergeben,
ersetzen sie (und werden nicht mit) denen aus markdown.* zusammengeführt,
was dazu führt, dass rehypeExpressiveCode lautlos aus der MDX-Pipeline
verschwindet.
3. Rendering-Skript im Post-Layout
Im Layout, das die Beiträge rendert
(in Astro Paper: PostDetails.astro),
wird ein <script> hinzugefügt, der mermaid importiert und clientseitig
rendert. Das Skript verwendet das Ereignis astro:page-load für
Kompatibilität mit View Transitions und einen MutationObserver, der bei
Theme-Wechsel (hell/dunkel) neu rendert:
import mermaid from "mermaid";
let themeObserver: MutationObserver | null = null;
function getTheme() { return document.documentElement.dataset.theme === "dark" ? "dark" : "forest";}
async function renderMermaid() { // Bereits gerenderte Diagramme zu pre.mermaid zurücksetzen für erneutes Rendern // (beim Theme-Wechsel erforderlich) document.querySelectorAll<HTMLElement>("[data-mermaid]").forEach(el => { const pre = document.createElement("pre"); pre.className = "mermaid"; pre.textContent = el.dataset.mermaid!; el.replaceWith(pre); });
const blocks = Array.from(document.querySelectorAll<HTMLPreElement>("pre.mermaid")); if (!blocks.length) return;
mermaid.initialize({ startOnLoad: false, theme: getTheme() });
await Promise.all(blocks.map(async pre => { const source = (pre.textContent ?? "").trim(); const id = `mermaid-${Math.random().toString(36).slice(2, 9)}`; try { const { svg } = await mermaid.render(id, source); const wrapper = document.createElement("div"); wrapper.className = "my-6 flex justify-center overflow-x-auto"; wrapper.dataset.mermaid = source; // Source für erneutes Rendern beim Theme-Wechsel speichern wrapper.innerHTML = svg; pre.replaceWith(wrapper); } catch (err) { console.error("[mermaid]", err); } }));}
document.addEventListener("astro:page-load", () => { renderMermaid(); themeObserver?.disconnect(); themeObserver = new MutationObserver(renderMermaid); themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"], });});// falschconst codeBlocks = Array.from(document.querySelectorAll("pre"));
// richtigconst codeBlocks = Array.from(document.querySelectorAll("pre:not(.mermaid)"));4. Theme-Erkennung
Die Website verwendet das Attribut data-theme am <html>-Element, um das
aktive Theme anzuzeigen ("light" oder "dark"). Die Funktion getTheme()
liest dieses Attribut und gibt den entsprechenden Mermaid-Theme-Namen zurück.
Dieser Blog verwendet "forest" für hell und "dark" für dunkel.
Farbanpassung mit CSS
Mermaid rendert SVG inline, und seine internen Styles verwenden
hochspezifische Selektoren (#id .klasse). Um sie mit den Theme-Farben der
Website zu überschreiben, ist !important im globalen CSS erforderlich.
In Mermaid v11 gibt es einige Besonderheiten gegenüber früheren Versionen:
- Flowcharts (
graph TD,flowchart LR) verwenden.node rect/circle/etc.für Knoten und.arrowheadPathfür Pfeilspitzen (in v10 war es.arrowMarkerPath). - Sequenzdiagramme verwenden
.actordirekt amrect(in v10 war es.actor rect),.messageLine0/.messageLine1für Nachrichtenlinien und[id$="-arrowhead"] pathfür Pfeilspitzen. - Mindmaps verwenden
spaninnerhalb vonforeignObjectfür den Abschnittstext. Die Farbe wird mit der CSS-Eigenschaftcolor:auf demspangesteuert, nicht mitfill:auf SVG-Elementen. themeVariablesbei der Mermaid-Initialisierung beeinflusst nicht die Abschnittsfarben in Mindmaps: Diese werden algorithmisch auf Basis des gewählten Basisthemas berechnet.
Das vollständige in diesem Blog verwendete CSS in global.css:
/* Mermaid: mindmap — nicht-Root-Abschnitte verwenden die Vordergrundfarbe des Themes */svg.mindmapDiagram [class*="section-"]:not(.section-root) span { color: var(--foreground) !important;}
/* Mermaid: flowchart — Knoten (graph TD, flowchart LR, etc.) */[id^="mermaid-"] .node rect,[id^="mermaid-"] .node circle,[id^="mermaid-"] .node ellipse,[id^="mermaid-"] .node polygon,[id^="mermaid-"] .node path { fill: var(--muted) !important; stroke: var(--border) !important;}
/* Mermaid: flowchart — Kanten */[id^="mermaid-"] .edgePath .path,[id^="mermaid-"] .flowchart-link { stroke: var(--accent) !important;}
/* Mermaid: flowchart — Pfeilspitzen (v11: .arrowheadPath) */[id^="mermaid-"] .arrowheadPath { fill: var(--accent) !important; stroke: var(--accent) !important;}
/* Mermaid: sequence — Akteure (v11: .actor direkt auf rect) */[id^="mermaid-"] .actor { fill: var(--muted) !important; stroke: var(--border) !important;}
/* Mermaid: sequence — Nachrichtenlinien */[id^="mermaid-"] .messageLine0,[id^="mermaid-"] .messageLine1 { stroke: var(--accent) !important;}
/* Mermaid: sequence — Pfeilspitzen */[id$="-arrowhead"] path { fill: var(--accent) !important; stroke: var(--accent) !important;}
/* Mermaid: sequence — vertikale Lifelines */[id^="mermaid-"] .actor-line { stroke: var(--border) !important;}