| name | fix-mermaid |
| description | Use this skill to fix Mermaid diagram syntax errors, rendering issues, and dark-mode styling problems inside HTML files. Trigger when the user mentions: "mermaid error", "Syntax error in text", "mermaid not rendering", "diagram is broken", "all diagrams crashed", "文字が読めない", "はみ出している", "図が醜い", "ダークモードで見にくい", "シーケンス図が切れている", "マインドマップの色がおかしい", or references a Mermaid version error (e.g. "mermaid version 10.9.5"). Covers: syntax fixes, SVG sizing, dark-mode coloring (mindmap/sequence), foreignObject CSS limitations, and font-loading timing.
|
Mermaid v10 修正・スタイリングスキル
Updated: 2026-06-15
対象
<div class="mermaid"> ブロック / JS テンプレートリテラル方式の構文エラー
- ダークモードでの配色崩れ(マインドマップ・シーケンス図)
- SVGサイズ・クリッピング問題
Part 1: 構文修正
Mermaid v10 の必須ルール
- コンテンツはカラム0配置(先頭空白なし)
- 各ステートメントは改行で分離(1行に複数連結しない)
- ノードラベル
A["text"] の内容は1行に収める
mindmap のみ例外 — 内部インデントは階層構造を表すため保持する
block-beta は使用禁止 — v10.9.5 で全体クラッシュの原因になる。graph TD で代替する
よくある原因(HTMLフォーマッタによる破壊)
- 14スペース等のHTMLインデントがMermaidコンテンツに混入する
- 長いノードラベルが行分断される(
A["テキスト と 続き"] に分かれる)
- 複数ステートメントが1行に連結される(
graph TD A["x"] B["y"] A --> B)
修正手順
grep_search で <div class="mermaid"> を全検索してブロック数を把握する
- 各ブロックを
view_file で確認し、上記ルール違反を特定する
replace_file_content / multi_replace_file_content で各ブロックの内容を修正する
<div> タグ自体のインデントは変更しない
- タグ内のMermaidコンテンツのみを置換対象にする
ダイアグラム別の注意点
| 種別 | 注意点 |
|---|
graph / flowchart | 最頻出。カラム0ルールを厳守 |
sequenceDiagram | Note over A,B: は1行に収める |
mindmap | 内部インデント保持(唯一の例外) |
block-beta | 使用禁止(全体クラッシュ) |
htmlLabels: true 環境 | < → <、> → > に変換 |
ブラウザレンダラーで Syntax Error を起こす文字・構文
| 箇所 | 問題のある記述 | 対処 |
|---|
subgraph ラベル | 丸括弧 () を含む | 削除または別表現に置換 |
subgraph ラベル | 絵文字(🌐 🖥️ 等) | 削除 |
participant ... as | 絵文字(👤 ⚡ 等) | 削除 |
エッジラベル |...| | 先頭スラッシュ |/command| | スラッシュを除去 |
ノードラベル ["..."] | 全角波ダッシュ 〜 | から 等の日本語に置換 |
ノードラベル ["..."] | スラッシュ path/to | - またはスペースに置換 |
菱形ノード {} | クォートなし日本語 {新しいファイル} | {"新しいファイル"} とクォートする |
Part 2: ダークモード配色・スタイリング(2026年6月追記)
重要: HTMLでMermaidをダークモードで使う場合、マインドマップ・シーケンス図には固有の落とし穴がある。
CSS セレクタだけで対処しようとすると失敗する。以下の手順に従うこと。
2-1: JSテンプレートリテラル方式(必須の前提)
<div class="mermaid"> に直接書くと IDEフォーマッタが破壊する。必ずJSテンプレートリテラル方式を使う。
<div id="diag-cli"></div>
<script>
const DIAGRAMS = {
'diag-cli': `mindmap
root((sandbox CLI))
サンドボックス管理
list - ls`,
};
mermaid.initialize({ startOnLoad: false, theme: 'dark', ... });
(async () => {
if (document.fonts) await document.fonts.ready;
for (const [id, src] of Object.entries(DIAGRAMS)) {
const { svg } = await mermaid.render('svg-' + id, src);
document.getElementById(id).innerHTML = svg;
}
})();
</script>
⚠️ フォントロード待ちが必須: await document.fonts.ready を省略すると、
Webフォント(Inter等)のロード前にMermaidが文字幅を計算するため、
ノード幅が狭くなり文字がはみ出す。必ず追加すること。
2-2: mermaid.initialize の themeVariables 設定
ダークモードの基本設定に加え、マインドマップ専用の cScale0〜11 を必ず設定する。
これを省略するとMermaidデフォルトの原色(赤・緑・紫)が使われ、ダークモードで非常に醜くなる。
mermaid.initialize({
startOnLoad: false,
theme: 'dark',
fontFamily: "'Inter', system-ui, sans-serif",
themeVariables: {
background: '#0a0a0a',
primaryColor: '#1e3a5f',
primaryTextColor: '#93c5fd',
primaryBorderColor: '#3b82f6',
lineColor: '#374151',
actorBkg: '#1e3a5f',
actorBorder: '#3b82f6',
actorTextColor: '#93c5fd',
actorLineColor: '#374151',
noteBkgColor: '#2d1b4e',
noteTextColor: '#d8b4fe',
cScale0: '#1e3a5f', cScale1: '#111827', cScale2: '#1a1a2e',
cScale3: '#1e2a3a', cScale4: '#121825', cScale5: '#111111',
cScale6: '#1a1f2e', cScale7: '#0f1923', cScale8: '#1e3a5f',
cScale9: '#111827', cScale10: '#1a1a2e', cScale11: '#1e2a3a',
},
flowchart: { curve: 'basis', padding: 20 },
sequence: { mirrorActors: true, noteMargin: 10, useMaxWidth: false },
});
2-3: SVGサイズ後処理(レンダリング後に必ず実行)
const svgEl = el.querySelector('svg');
if (svgEl) {
svgEl.removeAttribute('width');
svgEl.removeAttribute('height');
svgEl.style.width = '100%';
svgEl.style.maxWidth = svgEl.style.maxWidth || '100%';
svgEl.style.height = 'auto';
svgEl.style.overflow = 'visible';
}
CSSにもフォールバックを追加:
.mermaid-wrap .mermaid {
overflow: visible;
}
.mermaid-wrap .mermaid svg {
overflow: visible;
}
2-4: マインドマップ ダークモード配色の落とし穴と対処法
❌ 以下の方法は機能しない(よくある間違い):
.mermaid svg .mindmap-node.depth-0 rect { fill: blue !important; }
✅ 正しい対処法(3段構え):
① ドキュメントhead CSSでforeignObject内HTMLをターゲット
#diag-cli foreignObject div,
#diag-cli foreignObject span,
#diag-cli foreignObject p,
#diag-best-practices foreignObject div,
#diag-best-practices foreignObject span,
#diag-best-practices foreignObject p {
color: #e2e8f0 !important;
-webkit-text-fill-color: #e2e8f0 !important;
}
② JS直接操作でノード背景のインラインstyleを上書き
function applyMindmapStyle(svgEl) {
const shapes = svgEl.querySelectorAll(
'g.mindmap-node > rect, g.mindmap-node > circle, '
+ 'g.mindmap-node > ellipse, g.mindmap-node > polygon, g.mindmap-node > path'
);
shapes.forEach(shape => {
shape.style.setProperty('fill', '#1e293b', 'important');
shape.style.setProperty('stroke', '#475569', 'important');
shape.style.setProperty('stroke-width', '1.5px', 'important');
});
const rootShape = svgEl.querySelector('g.mindmap-node > circle, g.mindmap-node > ellipse');
if (rootShape) {
rootShape.style.setProperty('fill', 'rgba(0,112,243,0.2)', 'important');
rootShape.style.setProperty('stroke', '#0070f3', 'important');
rootShape.style.setProperty('stroke-width', '2.5px', 'important');
rootShape.style.setProperty('filter', 'drop-shadow(0 0 10px rgba(0,112,243,0.5))', 'important');
}
svgEl.querySelectorAll('g.mindmap-node text').forEach(t => {
t.style.setProperty('fill', '#e2e8f0', 'important');
});
svgEl.querySelectorAll('g.mindmap-node foreignObject').forEach(fo => {
try {
fo.querySelectorAll('*').forEach(child => {
child.style.setProperty('color', '#e2e8f0', 'important');
});
} catch (e) { }
});
svgEl.querySelectorAll('path').forEach(p => {
if (p.getAttribute('fill') === 'none'
|| p.classList.contains('mindmap-edge')
|| p.classList.contains('edge')) {
p.style.setProperty('stroke', '#4b5563', 'important');
}
});
requestAnimationFrame(() => {
svgEl.querySelectorAll('g.mindmap-node foreignObject').forEach(fo => {
try {
fo.querySelectorAll('*').forEach(child => {
child.style.setProperty('color', '#e2e8f0', 'important');
});
} catch (e) { }
});
});
}
③ レンダー後に呼び出す
const MINDMAP_IDS = ['diag-cli', 'diag-best-practices'];
if (MINDMAP_IDS.includes(id)) {
applyMindmapStyle(svgEl);
}
2-5: シーケンス図 下部切れ問題
症状: 最後のメッセージや Note、アクターボックス(下段)が切れて見えない。
原因と対処:
| 原因 | 対処 |
|---|
mirrorActors: false でアクターが上段のみ | mirrorActors: true に変更 |
| SVGのheight属性を削除した後にviewBoxが存在しない | overflow: visible をSVGに設定 |
useMaxWidth のデフォルト制限 | useMaxWidth: false を設定 |
sequence: { mirrorActors: true, noteMargin: 10, useMaxWidth: false }
svgEl.style.overflow = 'visible';
2-6: ノードラベルのはみ出し(文字切れ)対策
ノード内テキストがはみ出す場合、以下を確認:
- フォントロード待ち:
await document.fonts.ready が実装されているか(最重要)
- ラベルにパディング用スペースを追加:
A["text"] → A[" text "](前後に半角スペース)
- サブグラフタイトルを短縮: 長いタイトルはノード幅計算に影響する
Part 3: JSテンプレートリテラル方式(恒久対策)
<div class="mermaid"> に直接書くと、VSCode/Prettier が保存のたびにインデントを付加して構文を壊す。
恒久対策は JS テンプレートリテラルへの移管。
<div class="mermaid">
graph LR
A --> B
</div>
<div id="diag-0"></div>
<script>
const DIAGRAMS = {
'diag-0': `graph LR
A --> B`,
};
mermaid.initialize({ startOnLoad: false });
(async () => {
if (document.fonts) await document.fonts.ready;
for (const [id, src] of Object.entries(DIAGRAMS)) {
const { svg } = await mermaid.render('svg-' + id, src);
document.getElementById(id).innerHTML = svg;
const svgEl = document.getElementById(id).querySelector('svg');
if (svgEl) {
svgEl.removeAttribute('width');
svgEl.removeAttribute('height');
svgEl.style.width = '100%';
svgEl.style.height = 'auto';
svgEl.style.overflow = 'visible';
}
}
})();
</script>
この方式では --> を --> にエスケープする必要もなくなる。
Part 4: React/Next.js (CSS Modules) 移植時の注意点
CSS Modules 環境下での中央寄せとサイズ制限
<div id="diag-0" className={styles.mermaid}>
<MermaidDiagram chart={DIAGRAM_0} />
</div>
.mermaid {
display: flex;
justify-content: center;
}
.mermaid :global(svg) {
display: block;
margin: 0 auto;
width: 100%;
max-width: 100%;
height: auto;
}
テスト環境(Vitest)でのモック化
vi.mock("@/components/docs/MermaidDiagram", () => ({
default: function DummyMermaidDiagram({ chart }: { chart: string }) {
return <pre data-testid="mermaid">{chart}</pre>;
},
}));
Part 5: Mermaidを諦めてHTML/CSSに置き換えるべきケース
以下は CSS では対処不能なため、純粋な HTML/CSS ウィジェットに置き換える:
flowchart TD で 5〜6 ノードを直列チェーン → 縦長 900px 超
- 接続されていない複数のサブグラフ(ノード数が非対称なためアスペクト比が崩れる)
判断基準:「ノード増減に関わらず、他の図と同じ高さに収まる保証がない場合」
クイックリファレンス:問題別対処フロー
| 症状 | まず確認すること | 対処 |
|---|
構文エラー (Syntax error in text) | インデント汚染・行連結 | Part 1参照 |
| 文字がノード外にはみ出す | document.fonts.ready の有無 | await document.fonts.ready を追加 |
| マインドマップが原色(赤・緑・紫) | cScale0-11 の設定 | Part 2-2参照 |
| マインドマップのテキストが読めない | foreignObject CSS非カスケード問題 | Part 2-4の手順に従う |
| シーケンス図の下部が切れる | mirrorActors, overflow の設定 | Part 2-5参照 |
CSS !important が効かない | inline style 属性の優先度 | JS .style.setProperty(..., 'important') を使う |
| SVGが縦長に拡大される | width: 100% の誤用 | width: auto; max-width: 100% に変更 |