Mermaid syntax for network topology: the full reference
Every piece of Mermaid flowchart syntax a network engineer needs, with the escaping rules, the styling that separates tiers, the renderer differences between GitHub and Obsidian, and the version bug that draws an arrowhead on a link you wrote as undirected.
- Write any campus or datacenter topology in Mermaid without looking up syntax.
- Escape hostnames and descriptions that contain characters Mermaid treats as syntax.
- Know which features render in GitHub, GitLab, Obsidian, and VS Code before you rely on them.
This is the reference version. The diagrams as code article covers the grammar in five minutes and why any of this is worth doing. This one is what you open when a diagram will not render, or when you need a shape you have not used before.
Ids and labels are different things
Every node has an id, which Mermaid parses, and optionally a label, which the reader sees.
core1["core-sw01.campus.example.com"]
In that line, core1 is the id and the quoted text after it is the label.
Ids cannot contain dots, dashes, spaces, or most punctuation. Hostnames contain all of those, which is why generating a diagram from real data always includes a sanitizing step:
def node_id(hostname):
return hostname.replace(".", "_").replace("-", "_")
Labels have no such restriction as long as they are quoted, so put the real hostname in the label and let the id be whatever the sanitizer produces.
Direction
The first line sets the layout direction.
| Write | Layout |
|---|---|
flowchart TD |
Top down. The default for tiered topology. |
flowchart LR |
Left to right. Good for paths and flows. |
flowchart BT |
Bottom up. |
flowchart RL |
Right to left. |
graph TD is the older keyword and still works, but flowchart is current and
supports shapes graph does not.
Links
The link operator carries the meaning, so pick it on purpose.
| Write | Draws | Use for |
|---|---|---|
a --- b |
Solid, no arrow | A physical cable. |
a === b |
Thick, no arrow | An uplink or port channel. |
a -.- b |
Dotted, no arrow | A standby or backup path. |
a --> b |
Solid with arrow | Direction that means something, such as traffic or a decision. |
a ==> b |
Thick with arrow | A primary directional path. |
a -.-> b |
Dotted with arrow | A conditional or fallback flow. |
a <--> b |
Arrows both ends | An explicitly bidirectional relationship. |
a --x b |
Cross at the end | A blocked or dropped path. |
a --o b |
Circle at the end | A terminated or open end. |
Length is controlled by how many dashes you type. a ---- b pushes the two
nodes further apart than a --- b, which is the only layout control the
flowchart renderer gives you. Use it when the layout engine crowds a diagram.
Labels on links
Text between pipes goes on the link:
core1 ---|"Te1/1/1 to Gi0/0/0"| edge1
There is an older form, core1 -- Te1/1/1 --- edge1, which reads badly with
interface names and breaks as soon as the label has punctuation. Use the pipes.
View diagram source - it's just text (Mermaid). Diagrams-as-code is how modern network docs work; the flagship course has a free module on it.
flowchart LR
acc["acc-sw01"]
core_a["core-sw01"]
core_b["core-sw02"]
fw["fw-edge01"]
acc ---|"Gi1/0/48"| core_a
acc -.-|"standby uplink"| core_b
core_a ===|"Po1, 2x10G"| core_b
core_a -->|"default route"| fwNode shapes
Shapes carry meaning cheaply, so a reader can tell a router from a subnet without a legend.
| Write | Shape | Conventional use |
|---|---|---|
a["text"] |
Rectangle | A device. |
a("text") |
Rounded | A service or process. |
a(["text"]) |
Stadium | A start or end point. |
a[["text"]] |
Subroutine | A subsystem drawn elsewhere. |
a[("text")] |
Cylinder | A database or store. |
a(("text")) |
Circle | A cloud, an internet edge, or a peering point. |
a{"text"} |
Diamond | A decision. |
a{{"text"}} |
Hexagon | A boundary, such as a firewall or a WAN edge. |
a[/"text"/] |
Parallelogram | Input or output. |
The backslash shapes, the alternate parallelogram and both trapezoids, are a
problem in any file where the diagram sits inside a JavaScript template
literal, because the backslash is consumed before Mermaid sees it. Avoid them
in MDX and JSX. Multi-line labels use <br/>:
core1["core-sw01<br/>10.10.0.11<br/>Catalyst 9500"]
Escaping
Wrap every label in quotes as a default habit. That handles spaces, slashes, and most punctuation. For characters the parser still treats as syntax, use entity codes:
| Character | Write |
|---|---|
" |
#quot; |
# |
#35; |
( and ) |
Safe inside quotes. |
< and > |
#lt; and #gt; |
An interface description such as uplink (primary) to "core" becomes:
acc1 ---|"uplink (primary) to #quot;core#quot;"| core1
If a diagram is generated from device data, put the escaping in the generator itself. Port descriptions are free text and somebody will eventually put a quote in one.
Grouping with subgraphs
A subgraph draws a box around nodes, which is how you show a site, a closet, or a tier:
subgraph idf2["Floor 2 IDF"]
acc1["acc-sw01"]
acc2["acc-sw02"]
end
The id is required if you want to link to the group as a whole. Subgraphs can carry their own direction, which is useful when the outer diagram is top down and one group reads better sideways:
subgraph core["Core"]
direction LR
core1["core-sw01"]
core2["core-sw02"]
end
View diagram source - it's just text (Mermaid). Diagrams-as-code is how modern network docs work; the flagship course has a free module on it.
flowchart TD
subgraph edge["WAN edge"]
rtr["rtr-edge01"]
fw{{"fw-edge01"}}
end
subgraph core["Core"]
direction LR
c1["core-sw01"]
c2["core-sw02"]
end
subgraph idf2["Floor 2 IDF"]
a1["acc-sw01"]
a2["acc-sw02"]
end
rtr --- fw
fw --- c1
fw --- c2
c1 === c2
c1 --- a1
c2 --- a2
a1 --- a2Styling a tier
Two mechanisms do different jobs.
Define a style with classDef, then apply it to nodes with class:
classDef core fill:#1f3a5f,stroke:#4a90d9,color:#fff
classDef access fill:#2a2a2a,stroke:#666,color:#ddd
class c1,c2 core
class a1,a2 access
There is a shorthand for a single node, c1:::core, which is convenient and
easy to miss when reading someone else’s diagram.
linkStyle targets links by index, counting from zero in the order they appear
in the source:
linkStyle 0,1 stroke:#d9534f,stroke-width:3px
linkStyle default marker-end:none
Index-based targeting is fragile. Insert a link in the middle of the diagram and
every later index shifts. In generated diagrams, emit the linkStyle lines from
the same loop that emits the links, so the indexes cannot drift apart.
Comments
A line starting with %% is a comment:
%% Generated by tools/generate_diagram.py. Do not edit by hand.
flowchart TD
For a generated file, that header is worth having. It tells the next person why their hand edit disappeared.
The undirected link that draws an arrow
Mermaid 11.4.x converts flowchart edges with an end marker defaulting to an
arrow point, and only overrides it for the arrow, circle, and cross link types.
An open link, a --- b, matches none of those cases, so it keeps the default
arrowhead. A physical topology written correctly with --- renders as though
every cable has a direction.
Upstream fixed this in 11.5.0. If you are pinned to an older version, override the end marker for the whole diagram:
flowchart TD
a["acc-sw01"]
b["core-sw01"]
a --- b
linkStyle default marker-end:none
Then restore the arrowhead on the links that should have one, by index. This is worth knowing before you conclude your own syntax is wrong, because the diagram renders without an error and simply says something you did not write.
What renders where
Each host ships its own Mermaid version, so support is not uniform.
| Renderer | Notes |
|---|---|
| GitHub | Renders in Markdown files, issues, and pull requests. Conservative version. |
| GitLab | Same idea, sometimes a different version than GitHub. |
| Obsidian | Renders in preview. Version tracks the app release. |
| VS Code | Needs an extension. Usually the newest of the group. |
| Notion | Code block set to Mermaid. Limited to common diagram types. |
Flowcharts, sequence diagrams, and state diagrams work everywhere. Newer node shapes, per-diagram config blocks, and alternate layout engines do not. If the same file has to render in more than one of these, write for the oldest one in the set and check it there rather than in the newest.
Where to go from here
To generate this from real device output instead of typing it, the LLDP article covers the parsing, and the network diagram generator runs it in your browser with no account.
The graded version, with a test suite over the generator functions, is in the free Mermaid lesson.