Something that keeps coming up in Word support threads: you put a diagram into Word and the labels are missing. The boxes render. The arrows render. The text inside them doesn't.
There's a second complaint that sounds unrelated, usually from someone who went through a converter: the drawing behaves like a photograph. You can't select a box, you can't retype a label, you scale it up and it goes soft.
Those two symptoms look like unrelated bugs. They're the same bug, and it isn't the file format.
It's one element inside the SVG — the element every diagram renderer uses to place a label.
Two ways Word can hold a drawing
Word has exactly two models for a drawing, and they aren't variants of each other.
The first is a picture. One object, one blob of pixels or vectors. You can move it, resize it, and that's the whole vocabulary.
The second is a set of native shapes — the same objects you get from Insert → Shapes. Each one has a geometry, a text frame you can click into and type, and a position in a z-order. Connectors are their own object type, and the important part is that a connector's endpoints can be bound to two specific shapes.
That binding is what makes a Word diagram feel alive: drag a box, and every arrow attached to it follows, because the arrow isn't drawn — it's computed from the two shapes it points at.
A picture has none of this. Which is why the difference between the two models is not "quality". It's whether the diagram survives its first revision.
Why Mermaid hits the foreignObject wall
Mermaid renders to SVG, and like most diagram tools it puts text inside shapes using <foreignObject>.
<foreignObject> is the standard trick for embedding a chunk of HTML inside an SVG drawing. It exists because raw SVG text layout is genuinely miserable — <text> plus manual <tspan> lines with hand-computed wrapping, no automatic line height, no rich inline styling. If you want a label that wraps and respects CSS, <foreignObject> is the only sane answer.
Word does not implement it.
And it doesn't degrade gracefully. The moment Word imports the SVG — before you've asked it to do anything — it translates the drawing into its own object model, keeps every geometric primitive, and drops the contents of the foreignObject. The box that held the label survives. The label doesn't. There's no error, no warning, and nothing recovers it: converting the picture into shapes afterwards hands you the same empty boxes. You find out when you're looking at a flowchart made of blank rectangles — or when someone else is.
That single unimplemented element is also the reason almost every "Mermaid to Word" converter hands you a PNG at 2× and calls it done. Seen from here, that isn't laziness. Given what Word does with foreignObject, a bitmap is the honest fallback: at least the labels stay readable. You just can't touch them.
Reproduce it yourself
Worth doing before you trust any of this, including the parts I got wrong at first:
Save an
.svgcontaining one plain<text>line and one<foreignObject>holding two<div>s.Insert it into Word (Insert → Pictures). It arrives as a single picture — and the damage is already done: the
foreignObjectbox is empty, while the plain<text>line is right there.Right-click → Convert to Shape, to rule out "it's in there and just not drawn". It stays empty.
Click the text. The plain
<text>line became a text box you can retype. TheforeignObjectblock never contained anything to click.
The first time I ran this I expected a slightly soft version of my labels, since "unsupported element" usually means it degrades to pixels. What came back was nothing — the box, the border, the rounded corners, and no text at all. No conversion, no intervention: that's what the import alone produced.
Three ways out, and what each one costs
Once you frame the problem as "the text lives somewhere Word can't read", the options become clear, and all three are defensible.
1. Rasterize. Render at 2× or 3× and embed the image. Cheap, universal, works for every diagram anyone will ever draw. Cost: zero editability, ever. It's also the option that quietly guarantees the diagram goes stale.
2. Keep the SVG, avoid foreignObject. If you export with text converted to <text> and <tspan>, Word reads the drawing as real vector shapes and you can scale it losslessly. Cost: the text becomes fixed geometry. You can move the whole label, you can't retype it. For a diagram that will never change, that's a fine trade. For an architecture doc, it isn't.
3. Drop the SVG entirely and emit native shapes. Walk the layout yourself, and produce one Word shape per node, one Word connector per edge, with the connector's stCxn / endCxn pointing at the real shape ids. Nothing is an image, and the text lives in a text frame.
Option 3 is the only one that survives someone saying "change this box". It's also the one almost nobody ships, because it means re-implementing the layout pass instead of calling a renderer.
The wall behind the wall
Escape foreignObject — export with the labels flattened to <text> and <tspan> — and the drawing inserts looking correct. Every label sits exactly where the browser put it. Option 2 appears to work.
Then someone right-clicks and chooses Convert to Shape, and it comes apart. The boxes keep their width. The text inside them doesn't: labels that fit a second ago run past the shape edge, or lose their trailing characters to a clip. Nothing was dropped this time. It's all there, and it's all in the wrong place.
The reason a <text> element is only half a fix: it carries an anchor point and a font size, but no box width. In SVG, how wide a string is belongs entirely to the renderer's font metrics. Before conversion Word isn't typesetting anything — it copies each coordinate and each font size into DrawingML, which is why the preview matches the browser exactly. After conversion, every shape owns its own text frame, and Word measures the string with its own metrics. Different engine, same box.
Our DOCX writer hit the same class of bug on a real Word build. We had written a node's font size as 24 half-points — 16px — against a live Chromium measurement of 14px. That's 14.3% wider than the box had been sized for, and Word doesn't shrink text to fit a fixed frame; it clips. A node reading Manual approval came through showing only Manual, its second line cut off by the fixed line height. An edge label reading No came back as N.
So "flatten to <text> and it scales losslessly" holds only while nobody edits it. The moment they do, you're back to predicting a text-measurement engine you don't own — which is precisely why option 3 can't just hand Word an SVG and hope for the best.
What emitting native shapes actually involves
This is where the work is, so it's worth being concrete about three things that surprised me.
The shape count is not the node count. In Mermaid, ([...]) is a stadium/capsule node, and the renderer gives it a separate text box layered on the shape. So a capsule node costs you two <wps:wsp> elements rather than one. A seven-node flowchart containing a single capsule node therefore yields 17 shapes instead of 16 — 7 nodes, 7 connectors, 2 edge labels, plus that extra text box. Switching the terminal node from ([...]) to [...] brings it to 16.
The lesson isn't Mermaid trivia. It's that "the counts match" is a weak assertion unless you know which renderer artifact you're counting. Ours carried a stale number in a handful of places for a while — the example diagram changed, the figure in the copy didn't. That's exactly the failure mode an assertion like this exists to catch, and it only catches it when the number is read out of the artifact rather than maintained by hand.
Connector binding is the whole feature. A connector that merely looks attached is drawn line art and will sit still when you drag a box. Real binding means writing both ends into the XML — start and end connection ids — and then verifying all of them resolved. In my case, 7 of 7, and that assertion is in the test suite, because a silent 6-of-7 is worse than a visible failure.
Layout has to be reimplemented, not reused. The renderer's <path> is the wrong abstraction for Word connectors. Connectors need to route between two shape boundaries, which means working from node geometry and edge routing rules, not from a flattened path. Anything you can't express that way — custom edge styling, hand-tuned routing, decorative markers — doesn't come through.
Where it breaks
A 7-node flowchart with straight-ish edges emits cleanly — the word matters here, because that's the shape-writing path, not Word's own Convert to Shape. Whether a 60-node hairball with custom class styling survives is a different question, and I'd rather flag it as the place I'd expect it to fail than imply I've proven otherwise — custom class styling and curved edge routing are the two things that have no clean equivalent in a Word connector.
I think that boundary is worth stating plainly, because the alternative is a tool that quietly lies about complex input. The honest version is: native shapes work well for diagrams whose value is that people need to edit them — process flows, org charts, architecture sketches — and poorly for diagrams whose value is that they look exactly a certain way.
That's most of the difference between "I exported my diagram" and "my diagram is now in the document".
I ended up building the option-3 path for Mermaid, running in the browser with no upload: filecabin.app/document/mermaid-to-word. The mechanism section above is the part I'd want regardless of which converter you use — what Word does with foreignObject is a Word behavior, not a tool's choice.
Top comments (0)