My apologies for the following AI generated analysis. But frankly i don't think i would have bothered investigating this if it weren't for AI.
classDef colors not applied to stadium, flag, and other roughjs-based node shapes; style keyword completely broken
Summary
Two related styling issues caused by the Forge iframe stripping inline style attributes from SVG elements:
style keyword completely broken on all shapes. style nodeId fill:#f00 has no effect on any node — rectangle, stadium, or otherwise.
classDef/class colors silently ignored on certain shapes. Stadium ([]), flag/asymmetric >], double circle ((())), and all @{ shape } syntax shapes render with the default theme fill instead of the user-specified classDef colors. Shapes like rectangle [], rounded (), hexagon {{}}, diamond {}, polygon [/ /], circle (()), and cylinder [()] render classDef colors correctly.
Environment
- Mermaid Diagrams Viewer: v2.79.0 (Confluence Cloud)
- Mermaid.js: 11.13.0 (bundled in the app)
- Browser: Chrome 146 (also tested in Edge)
- Confluence: Cloud instance, dark and light themes
Steps to Reproduce
Issue 1: style keyword broken on all shapes
- Create a Confluence page with a code block (language:
mermaid) containing:
flowchart LR
A["Rectangle"]
B("Rounded")
style A fill:#be123c,stroke:#fb7185,color:#fff
style B fill:#be123c,stroke:#fb7185,color:#fff
- View the page.
Expected: Both nodes render with red fill.
Actual: Both nodes use default theme fill. The style keyword has no visible effect.
Issue 2: classDef broken on specific shapes
- Create a Confluence page with a code block (language:
mermaid) containing:
flowchart LR
A["Rectangle"]
B("Rounded")
C(["Stadium"])
D{{"Hexagon"}}
classDef red fill:#be123c,stroke:#fb7185,color:#fff
class A,B,C,D red
- View the page.
Expected: All four nodes render with red fill.
Actual: Rectangle, Rounded, and Hexagon are red. Stadium uses the default theme fill (gray/dark).
Root Cause Analysis
We connected to the Forge iframe contexts via Chrome DevTools Protocol and inspected the rendered SVG. The issue involves two interacting factors:
Factor 1: Forge iframe strips inline style attributes
The Forge platform's iframe sandbox removes all inline style attributes from SVG elements. This was confirmed by inspecting every shape element inside the Forge iframes — all have style="null" regardless of shape type. Mermaid.js sets inline style attributes on shape elements as part of classDef application (e.g., style="fill:#be123c !important;stroke:#fb7185 !important"), but these are stripped before the SVG is painted.
This single factor completely breaks the style keyword on all shapes. Mermaid's style nodeId fill:#f00 applies styling only via inline style attributes — it does not generate any CSS rules in the <style> block. Since Forge strips all inline style attributes, style directives have zero effect regardless of node shape.
By contrast, classDef/class generates CSS rules inside a <style> element in the SVG (which Forge preserves), so it works — except on shapes where the CSS selector doesn't reach the visual element (Factor 2 below).
Factor 2: Mermaid's classDef CSS only targets direct children
Mermaid.js also generates CSS rules inside a <style> block in the SVG. These rules use the child combinator (>):
.red > * { fill: #be123c !important; stroke: #fb7185 !important; }
This selector only matches direct children of the .red node group.
Why this matters
Different node shapes produce different SVG structures:
Rectangle (works): The <rect> is a direct child of the node <g>:
<g class="node default red"> ← .red
<rect class="basic label-container"> ← .red > * ✅ MATCHES
<g class="label">
Stadium (broken): roughjs wraps <path> elements in an extra <g>:
<g class="node default red"> ← .red
<g class="basic label-container outer-path"> ← .red > * ✅ matches (but <g> is not the visible shape)
<path d="..." fill="#1f2020"> ← .red > * > path ❌ GRANDCHILD, NOT MATCHED
<path d="..." fill="none" stroke="#ccc"> ← .red > * > path ❌ GRANDCHILD, NOT MATCHED
<g class="label">
The CSS .red > * matches the wrapper <g>, but the actual visual <path> elements are grandchildren — the CSS selector doesn't reach them. Normally, mermaid compensates by applying inline style attributes directly to the <path> elements, but the Forge iframe strips those.
Computed style evidence
From CDP Runtime.evaluate inside the Forge iframes:
| Element |
computed-fill |
Expected |
Rectangle <rect class="basic label-container"> |
rgb(30, 64, 175) ✅ |
blue |
Stadium <g class="basic label-container outer-path"> |
rgb(30, 64, 175) ✅ |
blue (but invisible — <g> has no visual) |
Stadium <path> (fill shape) |
rgb(31, 32, 32) ❌ |
should be blue, gets default theme |
Stadium <path> (stroke shape) |
rgb(204, 204, 204) ❌ |
should be blue stroke, gets default |
Affected shapes
Shapes that always use roughjs and wrap <path> elements in an extra <g> (from mermaid source inspection):
| Shape |
Mermaid syntax |
Source file |
Uses roughjs always? |
| Stadium |
([Label]) |
shapes/stadium.ts |
Yes |
| Half-Rounded Rectangle (Flag) |
>Label] |
shapes/halfRoundedRectangle.ts |
Yes |
| Double Circle |
(((Label))) |
shapes/doubleCircle.ts |
No, but uses <g> wrapper with two <circle> children |
Shapes that use native SVG elements as direct children (these work):
- Rectangle →
<rect> direct child
- Hexagon →
<polygon> via insertPolygonShape() direct child
- Diamond →
<polygon> via insertPolygonShape() direct child
- Cylinder →
<path> direct child with class basic label-container
Suggested Fix
Option A: Post-process the SVG in the app (workaround)
In diagram.tsx, after mermaid.render() returns the SVG string, rewrite the <style> block to add descendant selectors alongside the child selectors. For each classDef rule like:
.red > * { fill: #be123c !important; }
Append an additional rule:
.red > g > path, .red > g > circle { fill: #be123c !important; }
This is a ~10 line change in useMermaidRenderSVG():
const { svg } = await mermaid.render('diagram' + String(Date.now()), code);
// Fix: classDef CSS uses .class > * which doesn't reach roughjs <path> grandchildren.
// The Forge iframe strips inline styles that would normally compensate.
const fixedSvg = svg.replace(/<style>([\s\S]*?)<\/style>/, (match, css) => {
const extraRules = [];
const regex = /(#\S+\s+\.(\w+))\s*>\s*\*\s*\{([^}]+)\}/g;
let m;
while ((m = regex.exec(css)) !== null) {
extraRules.push(`${m[1]} > g > path{${m[3]}}`);
extraRules.push(`${m[1]} > g > circle{${m[3]}}`);
}
return `<style>${css}\n${extraRules.join('\n')}</style>`;
});
setSvg(fixedSvg);
Option B: Convert inline styles to SVG attributes (alternative)
Before passing the SVG to <SVG>, parse elements with style attributes and convert CSS properties that have equivalent SVG presentation attributes (fill, stroke, stroke-width, etc.) into element attributes which the Forge sandbox preserves.
There is also fix in mermaid.js (upstream)
Might take a while... ? Since this really is not a use case that mermaid.js probably cares too much about (and they have 1.3K issues) but i logged a detailed ticket anyway...
mermaid-js/mermaid#7596
The proper long-term fix is in mermaid's theme CSS generation — change .className > * to .className > *, .className > g > path, .className > g > circle so the generated CSS reaches grandchild elements. This would fix the issue in any environment that sanitizes inline styles.
Test Pages
We created 8 test pages on our confluence site with 40+ diagrams systematically testing every shape, style, and edge combination:
- Test 1-- classDef Basics -- all pass (rectangles only)
- Test 2 -- Node Shapes -- stadium, flag, double circle, fails, all others pass
- Test 3 -- New Shapes -- All v11 shapes fail
Verification
The same mermaid code renders all shapes correctly (with classDef colors) in:
- A standalone HTML page with mermaid.js 11.13.0 and
securityLevel: 'antiscript'
- The Mermaid Live Editor (https://mermaid.live)
The issue is specific to the Forge iframe environment where inline styles are stripped.
My apologies for the following AI generated analysis. But frankly i don't think i would have bothered investigating this if it weren't for AI.
classDef colors not applied to stadium, flag, and other roughjs-based node shapes;
stylekeyword completely brokenSummary
Two related styling issues caused by the Forge iframe stripping inline
styleattributes from SVG elements:stylekeyword completely broken on all shapes.style nodeId fill:#f00has no effect on any node — rectangle, stadium, or otherwise.classDef/classcolors silently ignored on certain shapes. Stadium([]), flag/asymmetric>], double circle((())), and all@{ shape }syntax shapes render with the default theme fill instead of the user-specified classDef colors. Shapes like rectangle[], rounded(), hexagon{{}}, diamond{}, polygon[/ /], circle(()), and cylinder[()]render classDef colors correctly.Environment
Steps to Reproduce
Issue 1:
stylekeyword broken on all shapesmermaid) containing:Expected: Both nodes render with red fill.
Actual: Both nodes use default theme fill. The
stylekeyword has no visible effect.Issue 2:
classDefbroken on specific shapesmermaid) containing:Expected: All four nodes render with red fill.
Actual: Rectangle, Rounded, and Hexagon are red. Stadium uses the default theme fill (gray/dark).
Root Cause Analysis
We connected to the Forge iframe contexts via Chrome DevTools Protocol and inspected the rendered SVG. The issue involves two interacting factors:
Factor 1: Forge iframe strips inline
styleattributesThe Forge platform's iframe sandbox removes all inline
styleattributes from SVG elements. This was confirmed by inspecting every shape element inside the Forge iframes — all havestyle="null"regardless of shape type. Mermaid.js sets inline style attributes on shape elements as part of classDef application (e.g.,style="fill:#be123c !important;stroke:#fb7185 !important"), but these are stripped before the SVG is painted.This single factor completely breaks the
stylekeyword on all shapes. Mermaid'sstyle nodeId fill:#f00applies styling only via inlinestyleattributes — it does not generate any CSS rules in the<style>block. Since Forge strips all inline style attributes,styledirectives have zero effect regardless of node shape.By contrast,
classDef/classgenerates CSS rules inside a<style>element in the SVG (which Forge preserves), so it works — except on shapes where the CSS selector doesn't reach the visual element (Factor 2 below).Factor 2: Mermaid's classDef CSS only targets direct children
Mermaid.js also generates CSS rules inside a
<style>block in the SVG. These rules use the child combinator (>):This selector only matches direct children of the
.rednode group.Why this matters
Different node shapes produce different SVG structures:
Rectangle (works): The
<rect>is a direct child of the node<g>:Stadium (broken): roughjs wraps
<path>elements in an extra<g>:The CSS
.red > *matches the wrapper<g>, but the actual visual<path>elements are grandchildren — the CSS selector doesn't reach them. Normally, mermaid compensates by applying inlinestyleattributes directly to the<path>elements, but the Forge iframe strips those.Computed style evidence
From CDP
Runtime.evaluateinside the Forge iframes:<rect class="basic label-container">rgb(30, 64, 175)✅<g class="basic label-container outer-path">rgb(30, 64, 175)✅<g>has no visual)<path>(fill shape)rgb(31, 32, 32)❌<path>(stroke shape)rgb(204, 204, 204)❌Affected shapes
Shapes that always use roughjs and wrap
<path>elements in an extra<g>(from mermaid source inspection):([Label])shapes/stadium.ts>Label]shapes/halfRoundedRectangle.ts(((Label)))shapes/doubleCircle.ts<g>wrapper with two<circle>childrenShapes that use native SVG elements as direct children (these work):
<rect>direct child<polygon>viainsertPolygonShape()direct child<polygon>viainsertPolygonShape()direct child<path>direct child with classbasic label-containerSuggested Fix
Option A: Post-process the SVG in the app (workaround)
In
diagram.tsx, aftermermaid.render()returns the SVG string, rewrite the<style>block to add descendant selectors alongside the child selectors. For each classDef rule like:Append an additional rule:
This is a ~10 line change in
useMermaidRenderSVG():Option B: Convert inline styles to SVG attributes (alternative)
Before passing the SVG to
<SVG>, parse elements withstyleattributes and convert CSS properties that have equivalent SVG presentation attributes (fill,stroke,stroke-width, etc.) into element attributes which the Forge sandbox preserves.There is also fix in mermaid.js (upstream)
Might take a while... ? Since this really is not a use case that mermaid.js probably cares too much about (and they have 1.3K issues) but i logged a detailed ticket anyway...
mermaid-js/mermaid#7596
The proper long-term fix is in mermaid's theme CSS generation — change
.className > *to.className > *, .className > g > path, .className > g > circleso the generated CSS reaches grandchild elements. This would fix the issue in any environment that sanitizes inline styles.Test Pages
We created 8 test pages on our confluence site with 40+ diagrams systematically testing every shape, style, and edge combination:
Verification
The same mermaid code renders all shapes correctly (with classDef colors) in:
securityLevel: 'antiscript'The issue is specific to the Forge iframe environment where inline styles are stripped.