From 75c14f3f69a767c340f430dc74bacd93089c81cd Mon Sep 17 00:00:00 2001
From: YBJ0000 <95595902+YBJ0000@users.noreply.github.com>
Date: Sun, 12 Jul 2026 17:10:25 +1000
Subject: [PATCH 1/3] fix: respect
line breaks in labels containing KaTeX
math
---
.changeset/katex-label-line-breaks.md | 5 +++
.../src/rendering-util/createText.spec.ts | 34 +++++++++++++++++++
.../mermaid/src/rendering-util/createText.ts | 4 +--
3 files changed, 41 insertions(+), 2 deletions(-)
create mode 100644 .changeset/katex-label-line-breaks.md
diff --git a/.changeset/katex-label-line-breaks.md b/.changeset/katex-label-line-breaks.md
new file mode 100644
index 00000000000..8cb8299f9fd
--- /dev/null
+++ b/.changeset/katex-label-line-breaks.md
@@ -0,0 +1,5 @@
+---
+'mermaid': patch
+---
+
+fix: `
` line breaks are no longer ignored in flowchart node and edge labels that contain `$$…$$` KaTeX math expressions. Each `
`-separated line now renders on its own row, matching the behavior of labels without math.
diff --git a/packages/mermaid/src/rendering-util/createText.spec.ts b/packages/mermaid/src/rendering-util/createText.spec.ts
index 0a46222682d..9da980106c6 100644
--- a/packages/mermaid/src/rendering-util/createText.spec.ts
+++ b/packages/mermaid/src/rendering-util/createText.spec.ts
@@ -99,4 +99,38 @@ describe('createText', () => {
expect(output.textContent).toEqual(expected);
}
);
+
+ it('renders text after
on a new line in labels containing KaTeX math', async () => {
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ const svgGroup = svg.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'g'));
+ const output = await createText(
+ select(svgGroup),
+ 'Input
$$x$$',
+ { useHtmlLabels: true },
+ { forceLegacyMathML: true, securityLevel: 'strict' }
+ );
+
+ const span = output.querySelector('span.nodeLabel');
+ const rows = [...(span?.children ?? [])];
+ expect(rows).toHaveLength(2);
+ expect(rows[0].outerHTML).toEqual('
Input
');
+ expect(rows[1].querySelector('.katex')).not.toBeNull();
+ });
+
+ it('renders each KaTeX line separately when
separates two math expressions', async () => {
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ const svgGroup = svg.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'g'));
+ const output = await createText(
+ select(svgGroup),
+ '$$x$$
$$f(x)$$',
+ { useHtmlLabels: true },
+ { forceLegacyMathML: true, securityLevel: 'strict' }
+ );
+
+ const span = output.querySelector('span.nodeLabel');
+ const rows = [...(span?.children ?? [])];
+ expect(rows).toHaveLength(2);
+ expect(rows[0].querySelector('.katex')).not.toBeNull();
+ expect(rows[1].querySelector('.katex')).not.toBeNull();
+ });
});
diff --git a/packages/mermaid/src/rendering-util/createText.ts b/packages/mermaid/src/rendering-util/createText.ts
index a9942d81376..a90ecc3f272 100644
--- a/packages/mermaid/src/rendering-util/createText.ts
+++ b/packages/mermaid/src/rendering-util/createText.ts
@@ -1,7 +1,7 @@
import { select } from 'd3';
import type { MermaidConfig } from '../config.type.js';
import type { SVGGroup } from '../diagram-api/types.js';
-import common, { hasKatex, renderKatexSanitized, sanitizeText } from '../diagrams/common/common.js';
+import { hasKatex, renderKatexSanitized, sanitizeText } from '../diagrams/common/common.js';
import type { D3TSpanElement, D3TextElement } from '../diagrams/common/commonTypes.js';
import { log } from '../logger.js';
import { profiler } from '../profiler.js';
@@ -47,7 +47,7 @@ async function addHtmlSpan(
const div = fo.append('xhtml:div');
const sanitizedLabel = hasKatex(node.label)
- ? await renderKatexSanitized(node.label.replace(common.lineBreakRegex, '\n'), config)
+ ? await renderKatexSanitized(node.label, config)
: sanitizeText(node.label, config);
const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel';
const span = div.append('span');
From 753f12ce22dc8b5ee8751957e56409e3cc52ddcf Mon Sep 17 00:00:00 2001
From: YBJ0000 <95595902+YBJ0000@users.noreply.github.com>
Date: Sun, 12 Jul 2026 17:55:05 +1000
Subject: [PATCH 2/3] docs: add JSDoc for addHtmlSpan
---
packages/mermaid/src/rendering-util/createText.ts | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/packages/mermaid/src/rendering-util/createText.ts b/packages/mermaid/src/rendering-util/createText.ts
index a90ecc3f272..966fa1aad09 100644
--- a/packages/mermaid/src/rendering-util/createText.ts
+++ b/packages/mermaid/src/rendering-util/createText.ts
@@ -30,6 +30,21 @@ function applyStyle(
// We assume that nobody will want to create labels larger than 16384 pixels wide
const maxSafeSizeForWidth = 16384;
+/**
+ * Creates a `` containing the label as HTML.
+ *
+ * If the label contains KaTeX (`$$…$$`) delimiters, it is rendered through
+ * `renderKatexSanitized`, which places each `
`-separated line in its own
+ * ``; otherwise the label is sanitized as plain HTML.
+ *
+ * @param element - The parent group to append the `
` to.
+ * @param node - The label text, its style, and whether it belongs to a node or an edge.
+ * @param width - The maximum width of the label.
+ * @param classes - Additional CSS classes for the label span.
+ * @param addBackground - Whether to add a background class to the containing div.
+ * @param config - Mermaid configuration object.
+ * @returns The created `` element.
+ */
async function addHtmlSpan(
element: D3Selection,
node: { label: string; labelStyle: string; isNode: boolean },
From 48f388c6c26a1f5bae0bce560050f962333dd448 Mon Sep 17 00:00:00 2001
From: YBJ0000 <95595902+YBJ0000@users.noreply.github.com>
Date: Sat, 25 Jul 2026 09:57:24 +1000
Subject: [PATCH 3/3] fix: respect hard line breaks in labels containing KaTeX
math
The KaTeX branch in createText bypasses markdownToHTML/nonMarkdownToHTML,
so hard line breaks in a label source never became
, and
renderKatexSanitized -- which splits its input on
-- saw a single
line. Apply the same \n ->
rule markdownToHTML uses for plain-text
tokens, rather than running the label through the markdown lexer, which
would wrap it in and leave the tags split across separate
s.
Diagnosis of the inputForKatex path is due to @omkarht in #7202.
Resolves #7194
Co-Authored-By: Claude Opus 5
---
.changeset/katex-label-line-breaks.md | 2 +-
.../src/rendering-util/createText.spec.ts | 37 +++++++++++++++++++
.../mermaid/src/rendering-util/createText.ts | 5 ++-
3 files changed, 42 insertions(+), 2 deletions(-)
diff --git a/.changeset/katex-label-line-breaks.md b/.changeset/katex-label-line-breaks.md
index 8cb8299f9fd..48294f39fe8 100644
--- a/.changeset/katex-label-line-breaks.md
+++ b/.changeset/katex-label-line-breaks.md
@@ -2,4 +2,4 @@
'mermaid': patch
---
-fix: `
` line breaks are no longer ignored in flowchart node and edge labels that contain `$$…$$` KaTeX math expressions. Each `
`-separated line now renders on its own row, matching the behavior of labels without math.
+fix: line breaks are no longer ignored in flowchart node and edge labels that contain `$$…$$` KaTeX math expressions. Both `
` and hard line breaks in the label source now render on their own row, matching the behavior of labels without math.
diff --git a/packages/mermaid/src/rendering-util/createText.spec.ts b/packages/mermaid/src/rendering-util/createText.spec.ts
index 9da980106c6..6e385b15fcd 100644
--- a/packages/mermaid/src/rendering-util/createText.spec.ts
+++ b/packages/mermaid/src/rendering-util/createText.spec.ts
@@ -133,4 +133,41 @@ describe('createText', () => {
expect(rows[0].querySelector('.katex')).not.toBeNull();
expect(rows[1].querySelector('.katex')).not.toBeNull();
});
+
+ it('renders hard line breaks on separate lines in labels containing KaTeX math', async () => {
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ const svgGroup = svg.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'g'));
+ // The flowchart lexer passes node text through verbatim, so a hard-wrapped label
+ // reaches createText with literal newlines rather than
.
+ const output = await createText(
+ select(svgGroup),
+ 'line 0\n line 1\n $$x=42$$',
+ { useHtmlLabels: true },
+ { forceLegacyMathML: true, securityLevel: 'strict' }
+ );
+
+ const span = output.querySelector('span.nodeLabel');
+ const rows = [...(span?.children ?? [])];
+ expect(rows).toHaveLength(3);
+ expect(rows[0].outerHTML).toEqual('line 0
');
+ expect(rows[1].outerHTML).toEqual('line 1
');
+ expect(rows[2].querySelector('.katex')).not.toBeNull();
+ });
+
+ it('keeps math intact when a hard line break follows a math expression', async () => {
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ const svgGroup = svg.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'g'));
+ const output = await createText(
+ select(svgGroup),
+ '$$a_1 + b_2$$\n$$x * y$$',
+ { useHtmlLabels: true },
+ { forceLegacyMathML: true, securityLevel: 'strict' }
+ );
+
+ const span = output.querySelector('span.nodeLabel');
+ const rows = [...(span?.children ?? [])];
+ expect(rows).toHaveLength(2);
+ expect(rows[0].querySelector('.katex')).not.toBeNull();
+ expect(rows[1].querySelector('.katex')).not.toBeNull();
+ });
});
diff --git a/packages/mermaid/src/rendering-util/createText.ts b/packages/mermaid/src/rendering-util/createText.ts
index 966fa1aad09..bee7f5261ab 100644
--- a/packages/mermaid/src/rendering-util/createText.ts
+++ b/packages/mermaid/src/rendering-util/createText.ts
@@ -349,7 +349,10 @@ export const createText = async (
const decodedReplacedText = await replaceIconSubstring(decodeEntities(htmlText), config);
//for Katex the text could contain escaped characters, \\relax that should be transformed to \relax
- const inputForKatex = text.replace(/\\\\/g, '\\');
+ //KaTeX labels bypass the markdown/non-markdown conversion above, so hard line breaks in the
+ //source label would never reach `renderKatexSanitized`, which splits its input on `
`.
+ //Convert them here using the same rule `markdownToHTML` applies to plain text.
+ const inputForKatex = text.replace(/\\\\/g, '\\').replace(/\n */g, '
');
const node = {
isNode,