Render Mermaid diagrams as beautiful SVGs or ASCII art
Ultra-fast, fully themeable, zero DOM dependencies. Built for the AI era.

Diagrams are essential for AI-assisted programming. When you're working with an AI coding assistant, being able to visualize data flows, state machines, and system architecture—directly in your terminal or chat interface—makes complex concepts instantly graspable.
Mermaid is the de facto standard for text-based diagrams. It's brilliant. But the default renderer has problems:
We built beautiful-mermaid at Craft to power diagrams in Craft Agents. It's fast, beautiful, and works everywhere—from rich UIs to plain terminals.
The ASCII rendering engine is based on mermaid-ascii by Alexander Grooff. We ported it from Go to TypeScript and extended it. Thank you Alexander for the excellent foundation! (And inspiration that this was possible.)
useMemo()npm install beautiful-mermaid
# or
bun add beautiful-mermaid
# or
pnpm add beautiful-mermaid
import { renderMermaidSVG } from 'beautiful-mermaid'
const svg = renderMermaidSVG(`
graph TD
A[Start] --> B{Decision}
B -->|Yes| C[Action]
B -->|No| D[End]
`)
Rendering is fully synchronous — no await, no promises. The ELK.js layout engine runs synchronously via a FakeWorker bypass, so you get your SVG string instantly.
Need async? Use renderMermaidSVGAsync() — same output, returns a Promise<string>.
import { renderMermaidASCII } from 'beautiful-mermaid'
const ascii = renderMermaidASCII(`graph LR; A --> B --> C`)
┌───┐ ┌───┐ ┌───┐
│ │ │ │ │ │
│ A │────►│ B │────►│ C │
│ │ │ │ │ │
└───┘ └───┘ └───┘
Because rendering is synchronous, you can use useMemo() for zero-flash diagram rendering:
import { renderMermaidSVG } from 'beautiful-mermaid'
function MermaidDiagram({ code }: { code: string }) {
const { svg, error } = React.useMemo(() => {
try {
return {
svg: renderMermaidSVG(code, {
bg: 'var(--background)',
fg: 'var(--foreground)',
transparent: true,
}),
error: null,
}
} catch (err) {
return { svg: null, error: err instanceof Error ? err : new Error(String(err)) }
}
}, [code])
if (error) return <pre>{error.message}</pre>
return
}
Why this works well:
- No flash — SVG is computed synchronously during render, not in a useEffect
- CSS variables — Pass var(--background) etc. instead of hex colors. The SVG inherits from your app's CSS, so theme switches apply instantly without re-rendering
- Memoized — Only re-renders when code changes
The theming system is the heart of beautiful-mermaid. It's designed to be both powerful and dead simple.
Every diagram needs just two colors: background (bg) and foreground (fg). That's it. From these two colors, the entire diagram is derived using color-mix():
const svg = renderMermaidSVG(diagram, {
bg: '#1a1b26', // Background
fg: '#a9b1d6', // Foreground
})
This is Mono Mode—a coherent, beautiful diagram from just two colors. The system automatically derives:
| Element | Derivation |
|---|---|
| Text | --fg at 100% |
| Secondary text | --fg at 60% into --bg |
| Edge labels | --fg at 40% into --bg |
| Faint text | --fg at 25% into --bg |
| Connectors | --fg at 50% into --bg |
| Arrow heads | --fg at 85% into --bg |
| Node fill | --fg at 3% into --bg |
| Group header | --fg at 5% into --bg |
| Inner strokes | --fg at 12% into --bg |
| Node stroke | --fg at 20% into --bg |
For richer themes, you can provide optional "enrichment" colors that override specific derivations:
const svg = renderMermaidSVG(diagram, {
bg: '#1a1b26',
fg: '#a9b1d6',
// Optional enrichment:
line: '#3d59a1', // Edge/connector color
accent: '#7aa2f7', // Arrow heads, highlights
muted: '#565f89', // Secondary text, labels
surface: '#292e42', // Node fill tint
border: '#3d59a1', // Node stroke
})
If an enrichment color isn't provided, it falls back to the color-mix() derivation. This means you can provide just the colors you care about.
All colors are CSS custom properties on the <svg> element. This means you can switch themes instantly without re-rendering:
// Switch theme by updating CSS variables
svg.style.setProperty('--bg', '#282a36')
svg.style.setProperty('--fg', '#f8f8f2')
// The entire diagram updates immediately
For React apps, pass CSS variable references instead of hex values:
const svg = renderMermaidSVG(diagram, {
bg: 'var(--background)',
fg: 'var(--foreground)',
accent: 'var(--accent)',
transparent: true,
})
// Theme switches apply automatically via CSS cascade — no re-render needed
15 carefully curated themes ship out of the box:
| Theme | Type | Background | Accent |
|---|---|---|---|
zinc-light |
Light | #FFFFFF |
Derived |
zinc-dark |
Dark | #18181B |
Derived |
tokyo-night |
Dark | #1a1b26 |
#7aa2f7 |
tokyo-night-storm |
Dark | #24283b |
#7aa2f7 |
tokyo-night-light |
Light | #d5d6db |
#34548a |
catppuccin-mocha |
Dark | #1e1e2e |
#cba6f7 |
catppuccin-latte |
Light | #eff1f5 |
#8839ef |
nord |
Dark | #2e3440 |
#88c0d0 |
nord-light |
Light | #eceff4 |
#5e81ac |
dracula |
Dark | #282a36 |
#bd93f9 |
github-light |
Light | #ffffff |
#0969da |
github-dark |
Dark | #0d1117 |
#4493f8 |
solarized-light |
Light | #fdf6e3 |
#268bd2 |
solarized-dark |
Dark | #002b36 |
#268bd2 |
one-dark |
Dark | #282c34 |
#c678dd |
import { renderMermaidSVG, THEMES } from 'beautiful-mermaid'
const svg = renderMermaidSVG(diagram, THEMES['tokyo-night'])
Creating a theme is trivial. At minimum, just provide bg and fg:
const myTheme = {
bg: '#0f0f0f',
fg: '#e0e0e0',
}
const svg = renderMermaidSVG(diagram, myTheme)
Want richer colors? Add any of the optional enrichments:
const myRichTheme = {
bg: '#0f0f0f',
fg: '#e0e0e0',
accent: '#ff6b6b', // Pop of color for arrows
muted: '#666666', // Subdued labels
}
Use any VS Code theme directly via Shiki integration. This gives you access to hundreds of community themes:
import { getSingletonHighlighter } from 'shiki'
import { renderMermaidSVG, fromShikiTheme } from 'beautiful-mermaid'
// Load any theme from Shiki's registry
const highlighter = await getSingletonHighlighter({
themes: ['vitesse-dark', 'rose-pine', 'material-theme-darker']
})
// Extract diagram colors from the theme
const colors = fromShikiTheme(highlighter.getTheme('vitesse-dark'))
const svg = renderMermaidSVG(diagram, colors)
The fromShikiTheme() function intelligently maps VS Code editor colors to diagram roles:
| Editor Color | Diagram Role |
|---|---|
editor.background |
bg |
editor.foreground |
fg |
editorLineNumber.foreground |
line |
focusBorder / keyword token |
accent |
| comment token | muted |
editor.selectionBackground |
surface |
editorWidget.border |
border |
graph TD
A[Start] --> B{Decision}
B -->|Yes| C[Process]
B -->|No| D[End]
C --> D
All directions supported: TD (top-down), LR (left-right), BT (bottom-top), RL (right-left).
stateDiagram-v2
[*] --> Idle
Idle --> Processing: start
Processing --> Complete: done
Complete --> [*]
sequenceDiagram
Alice->>Bob: Hello Bob!
Bob-->>Alice: Hi Alice!
Alice->>Bob: How are you?
Bob-->>Alice: Great, thanks!
classDiagram
Animal <|-- Duck
Animal <|-- Fish
Animal: +int age
Animal: +String gender
Animal: +isMammal() bool
Duck: +String beakColor
Duck: +swim()
Duck: +quack()
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE_ITEM : contains
PRODUCT ||--o{ LINE_ITEM : "is in"
Use linkStyle to override edge colors and stroke widths — just like Mermaid's linkStyle:
graph TD
A --> B --> C
linkStyle 0 stroke:#ff0000,stroke-width:2px
linkStyle default stroke:#888888
| Syntax | Effect |
|---|---|
linkStyle 0 stroke:#f00 |
Style a single edge by index (0-based) |
linkStyle 0,2 stroke:#f00 |
Style multiple edges at once |
linkStyle default stroke:#888 |
Default style applied to all edges |
Index-specific styles override the default. Supported properties: stroke, stroke-width.
Works in both flowcharts and state diagrams.
Bar charts, line charts, and combinations — using Mermaid's xychart-beta syntax.
Bar chart:
xychart-beta
title "Monthly Revenue"
x-axis [Jan, Feb, Mar, Apr, May, Jun]
y-axis "Revenue ($K)" 0 --> 500
bar [180, 250, 310, 280, 350, 420]
Line chart:
xychart-beta
title "User Growth"
x-axis [Jan, Feb, Mar, Apr, May, Jun]
line [1200, 1800, 2500, 3100, 3800, 4500]
Combined bar + line:
xychart-beta
title "Sales with Trend"
x-axis [Jan, Feb, Mar, Apr, May, Jun]
bar [300, 380, 280, 450, 350, 520]
line [300, 330, 320, 353, 352, 395]
Horizontal orientation:
xychart-beta horizontal
title "Language Popularity"
x-axis [Python, JavaScript, Java, Go, Rust]
bar [30, 25, 20, 12, 8]
Axis configuration:
x-axis [A, B, C]x-axis 0 --> 100x-axis "Category" [A, B, C]y-axis "Score" 0 --> 100Multi-series: Add multiple bar and/or line declarations. Each series gets a distinct color from a monochromatic palette derived from the theme's accent color.
The chart renderer follows a clean, minimal design philosophy inspired by Apple and Craft:
interactive: true, hovering over bars or data points shows value tooltips. Multi-line tooltips appear when multiple series share an x-position--xychart-color-N), so theme changes apply instantly without re-renderingFor terminal environments, CLI tools, or anywhere you need plain text, render to ASCII or Unicode box-drawing characters:
import { renderMermaidASCII } from 'beautiful-mermaid'
// Unicode mode (default) — prettier box drawing
const unicode = renderMermaidASCII(`graph LR; A --> B`)
// Pure ASCII mode — maximum compatibility
const ascii = renderMermaidASCII(`graph LR; A --> B`, { useAscii: true })
Unicode output: ``` ┌───┐ ┌───┐ │ │ │ │ │ A │
$ claude mcp add beautiful-mermaid \
-- python -m otcore.mcp_server <graph>