Diagrams (Mermaid)

Read the guideMermaid diagrams in a note Guide

How it works

Mermaid diagrams. A ```mermaid fence holding a flowchart (flowchart/graph, in all four directions), a sequence diagram (sequenceDiagram) or a pie chart (pie) is drawn as a diagram in reading mode, in the exported file and in the answers of Ask your notes — which is where most diagrams come from. The drawing follows the note theme (ink, paper and accent), so it matches the surrounding text in both themes. In formatted editing — the default mode — the diagram is also drawn, with the block bar on top (copy, edit, delete). Edit opens a bar at the bottom with the source: the note is locked while it is open and the drawing is redone as you type; closing applies it (there is no save and no cancel), Ctrl+Z undoes the whole change in one step, and emptying the field removes the block. In split mode, clicking the diagram takes the cursor to the fence in the raw text. In the text editor everything stays text: press Ctrl+E to see the diagram. Inside a list item, the diagram — and also the SMILES structure, the block formula, [files] and ![[note]] — stays as text in formatted editing and appears drawn in reading mode: there the item's marker would have no way back into the file.

Subgraphs (subgraph … end, nested ones included) become boxes with a title, and edges cross the border starting from the real node; a subgraph id can also be an edge endpoint (sub --> X). Author-declared colors apply: classDef, class, :::class and style (fill, stroke, width, dashes and text color). To start one, use Insert → Diagram (Mermaid) in the note toolbar: it drops in a ready skeleton for the type you pick. The menu offers the six most used directly; All diagrams… opens a picker with all 33, grouped by what they are for, with search (by name or by what the diagram shows) and a rendered preview of each one — there are 33 drawings, and seeing the drawing decides faster than reading the name. The diagram frontmatter — the block between --- before the first line — is read too: title becomes the title drawn above the diagram (a title in the body wins) and the config keys LightNote knows reach the diagram (gitGraph.mainBranchName, gantt.displayMode, and the packet ones: bitsPerRow, bitOrder: descending — which mirrors the row — and showBits: false); what it does not know is ignored, without refusing the note.

Sequence, entities, classes and states

In the sequence diagram you get participant/actor (with as), the arrow forms (->>, -->>, -x, -)), activation (+/- and activate/deactivate), notes (Note left of/right of/over), the loop/alt/else/opt/par blocks and autonumber. Whatever is not drawn yet — other diagram types — keeps showing up as a code block, colored as before; direction inside a subgraph and linkStyle are ignored. The Mermaid v11 node syntax (A@{ shape: rounded, label: "text" }) is read, and the v11 shape catalog is drawn: doc, lin-doc, docs, tag-rect, win-pane, hourglass, bolt, flag, das… — each one is the symbol its name says, and none of them falls back to a rectangle, because a rectangle in place of a document would be another diagram. A name Mermaid does not have turns the diagram back into a code block, with the line pointed out. The edge id (A e1@--> B) and e1@{ animate: true } are accepted and ignored: the animation is not drawn. A typed participant (participant DB@{ "type": "database" }) draws the UML symbol above the lane — boundary, control, entity, database, queue and collections — and the "alias" declared there works like as.

The erDiagram is drawn too: each entity becomes a box with its attributes and the cardinality shows at both ends of the relationship (||--o{, }o--||, |o, |{); -- is an identifying relationship and .. a non-identifying one. The alias p[Person] names the box without changing the id the relationships refer to. subgraph … end works here too: the entities inside get a box with a title, subgraphs nest, and the box id can be one end of a relationship (CUSTOMER ||--o{ Sales : places), attaching to the boundary instead of to an entity by that name.

The classDiagram too: a three-compartment box (name, attributes and methods, split by the method parentheses), <<interface>> on its own line, namespace as a box and the six relationships (<|--, *--, o--, -->, ..>, ..|>). Mind the direction: in A <|-- B the triangle sits on A — the one that inherits is B. Quoted cardinality shows at both ends ("1" --> "*"). Generics List~T~ show up as List<T>, in members too. The lollipop interface (bar ()-- foo) draws the circle on top of the box on the side where the () is — and only there — with the relationship touching the circle; namespace blocks also nest, and after the inner } a class goes back to the outer namespace.

The stateDiagram-v2 draws the state machine: [*] is the start (filled disc) and the end (disc with a ring), state X { … } becomes a box, <<fork>>/<<join>> become a bar and <<choice>> a diamond. A -- inside a composite state separates the concurrent regions, marked by a dashed line.

Timeline, journey, quadrants and Gantt

The timeline is drawn too: each period becomes a column of the timeline, with its events stacked underneath, and section groups the eras into colored bands. Both spellings for several events work — colons on the same line (2004 : Facebook : Google) and a continuation on the next line (: another event) —, and here any line that is not title, section or direction is free text. direction TD (a vertical timeline) is not drawn yet and keeps the code block.

The journey (user journey) is drawn too: each task becomes a box on the journey axis, and the score from 1 to 5 moves its face up or down — it is the profile they form that the diagram exists to show. section groups the phases, and the actors become colored markers on the task, with a legend on the left. Here the grammar is strict (Task: score: Actor, Actor), unlike the timeline's: without the name/score pair there is nowhere to put the face, so a malformed line — or a score outside 1 to 5 — refuses the diagram, pointing at the line.

And the quadrantChart draws the four-quadrant plane: x-axis and y-axis name the ends of each axis (the right-hand side of --> is optional), quadrant-1 to quadrant-4 label the regions, and each point goes in as Name: [x, y], with x and y from 0 to 1. Mind the numbering: it is the one from the cartesian plane, not from reading order — quadrant-1 is the top-right one, and from there it goes counter-clockwise; y = 1 is at the top. A point's color and radius can come directly (P: [0.5, 0.5] radius: 10, color: #109060) or from a class (P:::class plus classDef), and the direct style wins over the class. A coordinate outside 0 to 1 refuses the diagram and points at the line instead of being clamped — it is the position on the plane, and clamping would put the point in a quadrant you did not write.

The gantt draws the schedule: dateFormat declares the format of the dates you write (default YYYY-MM-DD) and axisFormat the one used for the axis labels (%m/%d); section groups the tasks into bands. Each task reads Text : tags, id, start, end — the tags done, active, crit and milestone come first and can be combined, the start is a date or after otherId, and the end is a date, a duration (5d, 2w, 3h, 45m) or until otherId. With a single field, the task starts where the previous one ended.

after may name a task declared further down the note; an id that exists nowhere — or a circular dependency — refuses the diagram and points at the line, instead of dropping the bar on today. excludes weekends (or excludes monday, or a date) pushes each task's end past the excluded days, and therefore changes how long the bars are; includes 2026-03-07 overrides the weekend, to say that this particular Saturday is a working day. A milestone shows up as a diamond, and todayMarker off removes today's line. displayMode compact stacks several tasks on the same row, and here a task only shares a row when nothing it occupies touches what is already there — the label included. The vert marker (Opening : vert, v1, 20:15, 0m) draws the instant as a vertical line crossing the whole chart, taking up no row of its own, and its label goes below the axis. dateFormat X (or x) reads the date as Unix time in seconds (or milliseconds), and on the axis %s/%Q writes it back. A tickInterval Mermaid does not know (1decade) is silently ignored, as it is there, and the axis goes back to automatic.

Mind map, requirements, Git and C4

In the mindmap the hierarchy comes from the indentation: an item's level is the column its text starts at, with no braces or arrows (a tab counts up to the next stop of 4, like in an editor). The first item is the root — a second item in its column would be a second root, and the diagram is then refused with the line pointed out. Each item may declare its shape: [square], (rounded), ((circle)), {{hexagon}}, ))bang(( and )cloud(; with no delimiter it comes out rounded. ::icon(...) and :::class are recognised and ignored (we do not ship the icon font, and the class refers to external CSS that does not exist here). The drawing has two sides — the root in the middle, the branches split left and right —, each branch with its own colour that the descendants inherit, and the twig gets thinner with depth.

It is a tree, not a force simulation like in mermaid: in a mind map the hierarchy is the content, so it has to be readable from the position — and, since the drawing is redone when the theme changes, a layout converging from random starting points would rearrange the whole map every time.

treeView (treeView-beta) draws a project’s folder tree, and it accepts both spellings: indentation (what you type) and the very characters of the tree command (├──, │, └──), which is what lets you paste terminal output straight into the note — indentation alone would not do there, because │ and a space take the same column and only the first says the branch goes on. A trailing slash marks a folder (src/), a name with spaces goes in quotes, ## text hangs a comment at the end of the line, and :::highlight highlights the name. icon(...) is recognised and ignored, like the mind map’s ::icon(): icon packs come from Iconify through JavaScript, and not even Mermaid itself draws them by default.

ishikawa (ishikawa-beta) draws the fishbone of a root-cause analysis: the first item is the EFFECT (the head, on the right), the next level are the categories (the bones) and the two inner ones are the causes and sub-causes. Three levels — a fourth one refuses the diagram with the line pointed out, because it does not exist in Mermaid. The bones alternate above and below the spine, which is what lets two neighbouring categories fit without one’s text landing on the other’s, and the order you write the causes in reads top to bottom on both sides.

venn (venn-beta) draws overlapping sets: set A["Desirable"] declares one, union A,B["Buildable"] names the shared region (a union is named by the sets it crosses) and text A1["React"], right below one of them, puts an item inside that region. It takes up to three sets, and a fourth refuses the diagram with the line pointed out: with four circles there is no arrangement that produces every region, and drawing an "almost right" one would hide a region you declared. The :number after the label (set A["Alpha"]:20) scales the circle — area follows the number — and not the intersections: a Venn with proportional intersections is a different diagram (Euler), which needs numeric optimisation. style A fill:#ff6b6b paints one set, and with a comma (style A,B color:#333) it applies to the union of those sets.

cynefin (cynefin-beta) draws Snowden’s framework for classifying decisions: the five domains have fixed names — complex, complicated, chaotic, clear and confusion — and fixed positions (the four quadrants, with confusion in the middle); what you choose is what goes inside each one. Each item goes in quotes, one per line, and complex --> complicated : "Pattern identified" draws the transition between two domains. A name outside the five refuses the diagram with the line pointed out and the list of what exists. The border between clear and chaotic comes out wavy: in the model it is a cliff, and the shape says so — here the wave is always the same, so the note does not change drawing every time it is opened.

swimlane (swimlane-beta) draws a process in lanes, one per owner: the syntax is the flowchart’s (same shapes, arrows, labels and classDef), and each subgraph stops being a box and becomes a lane. Every node must sit inside one — a loose node refuses the diagram, because there would be no way to say whose task it is — and a lane inside a lane refuses too. With LR the lanes are rows and the flow runs right; with TB they are columns and the flow runs down. The position along the flow comes from the longest path to each task, and the band is the owner’s: a task never leaves its lane, even when that would make the drawing more compact.

agentflow (agentflow-beta) draws an AI agent flow, and it is another vocabulary for the flowchart: the arrows are the same, flow id["Name"] … end plays the part of subgraph (and nests), global … end holds what every agent consults, and connector id["Name"] declares an outside system. What really changes is the shape, which says each step’s role: @{ shape: input }, task, tool (a tool call), decision, refdoc (reference document) and action (the final action). Attach the document with -.-, no arrow: it is consulted, not a step of the flow. The keys that describe the agent in words (instruction, params, returns, connectorRef, protocol, endpoint) are read and ignored in the drawing. These shape names only work here — in a flowchart they refuse, because Mermaid does not have them there either.

usecase (usecase-beta) draws UML’s use case diagram: whatever is declared with actor becomes the stick figure, and everything else is a use case, drawn as an ellipse — that is the distinction the type exists to show. A use case can be written as Id("Label"), Id[Label], just Id, or just the quoted text; systemBoundary "Name" … end is the system boundary and note for X "text" attaches a note. The relations are UML’s: --> associates, --|> is generalization (the hollow triangle sits on the general one) and A ..> : include B — with the target after the colon — draws the dotted « include », which also works for extend. What describes the actor without changing the drawing (type, business, icon) is read and ignored, and <<Stereotype>> goes into the label. The json node is not drawn yet and keeps the code block: it is a multi-line block with the data tree inside, and there the JSON stays readable as text.

wardley (wardley-beta) draws a Wardley map: a plane whose vertical axis is visibility to the user (the top is what they see) and whose horizontal axis is evolution, from Genesis to Commodity.

Mind the order of the pair: component Tea [0.63, 0.81] is [visibility, evolution] — the first number is the vertical axis, and read the other way the map still draws and starts telling a different strategy. anchor is the user at the top, A -> B links the chain (a name may contain spaces, quoted or not), A +> B is flow (thicker; +<> both ways and +'text'> with a label) and evolve Kettle 0.62 draws the dashed arrow to where the piece should get to. Also supported: evolution (renames the axis stages), size, (build)/(buy)/(outsource)/(market), (inertia) (the resistance bar), label [dx, dy], pipeline X { … }, note, accelerator/deaccelerator and numbered annotations with the annotations legend.

eventmodeling draws an event model: a timeline in three lanes — the screen on top, the command (and the read model) in the middle, the event at the bottom. Each line reads tf 02 cmd AddItem: the number is the moment (the column) and the middle word is the type, which picks the lane — ui, cmd/command, pcr/processor, rmo/readmodel, view and evt/event (the long forms work the same). The lane is not declared: it is the method. rf (resetframe) opens a new slice, with a dashed divider — that is where the story restarts. A frame’s schema goes in braces ({ description: string }) or by reference ([[Data]]) to a data Data { … } block, which may come later; and ->> 02 ->> 03 links the frame to the moments that feed it, by number.

railroad (railroad-ebnf-beta, -abnf-beta, -peg-beta or railroad-beta) draws a grammar as railway tracks: each rule becomes a path you can follow with a finger, and what you can write is what you can walk. A rule is name = expression ; (or name <- … ; in PEG) and may span several lines — it closes at the ;. Quoted text is a terminal (drawn as a capsule) and a bare name is another rule (a rectangle); | (or /) is choice, ? optional, + one or more, * zero or more, and ( ), [ ] and { } group, make optional and repeat. ABNF puts the repetition first (1*( ALPHA / "-" ), *DIGIT), PEG accepts the predicates !x and &x — which go into the label, because they consume no input — and railroad-beta takes the function spelling (sequence(choice(terminal("+"), …))). All four land on the same drawing: the bypass above is the optional and the loop below, with the arrow pointing back, is the repetition.

The requirementDiagram draws requirements and elements as compartment boxes: the declared type (requirement, functionalRequirement, performanceRequirement, interfaceRequirement, physicalRequirement, designConstraint) appears in guillemets above the name, and the fields id, text, risk and verifymethod become rows — in an element, type and docref. The relationships (satisfies, traces, derives, verifies, refines, copies, contains) are written a - satisfies -> b or mirrored as b <- satisfies - a, and in the mirrored form the first name is the destination. Only contains is a solid line, with the symbol on the side of whoever contains; the others are dashed with the arrow at the destination. A name with a space goes in quotes.

The gitGraph draws a repository history: each commit moves forward in time, branch name opens a new lane and starts working on it (like git switch -c), checkout goes back to another one and merge joins the two. Attributes take a colon: commit id: "text" tag: "v1" type: HIGHLIGHT (also REVERSE), and branch name order: 2 changes the lane order; cherry-pick id: "…" requires the id of a commit that exists. The shape tells the type — a filled dot for an ordinary commit, a square for a highlighted one, a circle with an X for a reverted one, a double ring for a merge and a cut circle for a cherry-pick —, because in a theme where two shades of the palette look alike only the shape separates them. LR: (the default), TB: and BT: turn the time axis. A checkout/merge of a branch that does not exist refuses the diagram and points at the line, instead of creating a lane you did not ask for.

The C4 family (C4Context, C4Container, C4Component, C4Dynamic and C4Deployment) is drawn as well, and in it everything is a macro — there is no arrow anywhere in the syntax. Each element becomes a box with the type in guillemets, the name in bold and the description below: Person(alias, "Name", "Description"), System, Container, Component and the Db, Queue and _Ext variants. In Container and Component the third argument is the TECHNOLOGY (Container(app, "App", "C++/Qt", "what it does")); in the others it is already the description. Boundaries (Boundary, Enterprise_Boundary, System_Boundary, Container_Boundary and the Node of C4Deployment) open a brace and become a titled box, nested ones included. The link is Rel(a, b, "label", "technology"); BiRel puts an arrow at both ends and Rel_Back points backwards. C4Dynamic numbers the relationships in the order they appear. The placement hints (Rel_U, Rel_D, Rel_L, Rel_R) and the Update…Style calls are accepted and ignored — the arrangement comes from the same layout as the other diagrams, and the colour comes from the note theme. A relationship naming an alias that was never declared refuses the diagram and points at the line, instead of inventing a box you did not write.

Board, charts and packet

In the kanban indentation is the syntax, with two levels: the outer one is the lane and the inner one is the card (id[Label], or just the text). The @{ ticket: LN-9, assigned: 'ana', priority: 'High' } after a card does not go into its text: ticket and assignee go to a footer of their own, and the priority (Very High, High, Low, Very Low) becomes the colour of the stripe on the left edge, on a warm-to-cold scale. All lanes end on the same line, and a lane with no card still shows — it is what tells you the column is empty. This is a drawn board, to explain a flow inside a note; the board you actually work on, with cards you drag, is the app's own Tasks panel.

The xychart (xychart-beta) draws bars and lines over two axes: x-axis "Title" [jan, feb, mar] gives the categories — a name with a space or a comma goes in quotes — y-axis "Title" 0 --> 100 fixes the scale, and each bar [10, 40, 90] or line [20, 30, 80] adds a series with one value per category, in the order they were declared. A series with more or fewer values refuses the diagram pointing at the line: the bars would slide onto other categories and the chart would start claiming something else. Without y-axis the scale comes from the data and starts at zero when the data does not go below it — starting at the smallest value exaggerates the difference between bars, and nobody asked for that; with a declared range, a value outside it refuses instead of overflowing the plot.

x-axis also takes a continuous range (0 --> 100), and without it the categories are the indices. Two bar series appear side by side, not overlapping as in mermaid — overlapping, the one behind disappears. xychart-beta horizontal is not drawn yet and keeps the code block, because drawing it vertically would hand you the chart transposed. A series can have a name (bar "Open" [10, 40], line "Closed" [20, 30]): the names become a legend above the plot, each with its series’ own shape — a filled bar, a line with a dot — because colour alone does not tell them apart. And in a line series a value can carry a label ([540 "PaLM", 65]), drawn above the point; on bars the label is ignored, as in Mermaid.

The radar (radar-beta) draws the profile of one or more curves over axes that radiate from the centre: axis a["Label"], b, c declares the axes — the line may repeat — and curve x["Name"]{80, 60, 90} gives one value per axis, in the order the axes were declared; the named form ({a: 80, c: 90, b: 60}) works too. Mind the direction: the first axis points up and the turn is clockwise. max and min fix the scale — without max it comes from the largest value read — and a value outside it refuses the diagram pointing at the line instead of being clamped; ticks changes the number of rings, graticule circle makes them round and showLegend false drops the legend. A curve with more or fewer values than axes also refuses: it would not be an incomplete drawing, it would be a different one.

The packet (packet-beta) draws the bit map of a packet: each field is 0-15: "Label", and the range is closed at both ends — 0-15 is sixteen bits. A single bit is written 4: "Label", and the relative form +16: "Label" takes the next sixteen, so you do not have to renumber everything when you insert a field in the middle. Fields must be contiguous: a gap or an overlap refuses the diagram pointing at the line, because either one would silently shift everything after it. A row holds 32 bits, and a field that crosses the turn is drawn on both, with the label in the wider part; the numbers above the grid mark where each field starts, plus the last bit of each row.

Grid, areas, architecture, flow and ZenUML

The block (block-beta) draws a grid of boxes, and in it the position is the order you wrote them in: columns 3 sets how many columns the grid has — without it everything goes in a single row — each box takes the flowchart shapes (a["Label"], b(("circle")), c{"rhombus"}), a:2 makes a box span two columns and space (or space:2) leaves a hole. block:id … end nests a grid inside another, with its own columns, and becomes a box around it. Arrows are the flowchart ones (a --> b, labelled as a -- "text" --> b), but here they only reference blocks already declared: an arrow to a name that does not exist refuses the diagram pointing at the line, instead of inventing a box in a cell you did not write. The gap between columns grows until the arrow label fits, so it never touches the neighbouring box. The block arrow (a<["Out"]>(right)) is a box shaped like an arrow: the directions go in parentheses (right, left, up, down, or x for both horizontal ones and y for both vertical ones) and combine ((x, down)); a made-up direction refuses the diagram with the line pointed out, instead of becoming just any arrow.

The treemap (treemap-beta) draws a map of areas: each rectangle has an area proportional to its value, and indentation nests, with free depth. A leaf declares the value ("Name": 12) and a section declares nothing — its value is the sum of its children, and a total of its own would allow a rectangle that lies about its own parts, so that refuses the diagram pointing at the line. A name with a space or a colon goes in quotes; classDef and :::class give colour. The arrangement is squarified, which keeps rectangles close to square — always slicing on the same axis would produce strips you cannot compare by eye, and comparing areas is the only thing this type does. Label and value appear only when they fit whole inside the rectangle: cut off at the border they would tell you nothing.

The architecture (architecture-beta) draws services and the links between them: service api(server)[API] declares a component, group cloud1(cloud)[Cloud] opens a boundary, the in cloud1 suffix puts the service inside it (groups nest) and junction j creates a corner to bend a link.

The side of the link is what decides the position: a:R -- L:b touches the right of a to the left of b, that is, it puts b to the right of a; L, R, T and B all work, and the arrowhead appears on the side of the < or the > (-->, <-->, or -- with no head at all). The {group} suffix on an end sends the line to the group boundary instead of the box inside it. The icons are the five Mermaid ships built in — cloud, database, disk, internet and server — and any other name refuses the diagram, pointing at the line and naming the ones that exist. The rest (logos:aws-lambda and the like) come from Iconify and only show up where whoever publishes the page registers the pack in JavaScript, which not even GitHub does: accepting them here would hand you a drawing that reproduces nowhere else. align row a b c (or align column) puts the members named on the same row — or in the same column — and a name that does not exist refuses the diagram with the line pointed out, instead of leaving the arrangement as it was without saying why.

The sankey (sankey-beta) draws a flow diagram in which the thickness of the ribbon is the value: the body has no arrows at all — it is a three-column CSV (source,target,value), one link per line, and repeating a name at both ends is what chains the flow. A label containing a comma goes in quotes, and a quote inside it is written doubled ("Fixed ""cost"""); the same source/target pair repeated is summed. A node's bar is as tall as the larger of what comes in and what goes out, so that the outgoing ribbons of a node that distributes more than it receives do not spill past the edge. The columns come from the longest path to each node, and whatever has no outflow goes to the last column. A value of zero or less, a link from a node to itself and a cycle refuse the diagram pointing at the line — in a cycle there is no longest path, and cutting a link in order to draw would show a flow you did not write. There is no title here: the body is plain CSV, and accepting one would draw fine in LightNote and be a syntax error in Mermaid.

The zenuml is another syntax for the same drawing — a sequence diagram — written as code instead of arrows. A synchronous call names only the target (Store.register(item)) and the sender is the block that encloses it: inside Store.register() { Stock.reserve() }, the one calling Stock is Store. At the outer level the caller is the starter — @Starter(Ann) names it, and without it a stick-figure User is used, appearing only if some call needs it. The asynchronous message is A->B: text (open head, nobody waits) and A->B.method() is the synchronous call with the sender spelled out. {} nests, return x goes back to whoever made the enclosing call (an if in between does not change that), x = A.m() is the other spelling of the same reply, new A(args) creates and // comment becomes a note above the next message. The blocks are if/else if/else, while/for/forEach/loop, opt, par and try/catch/finally — and } else { continues the same frame with a divider instead of opening another. @Actor draws the stick figure; the other annotators (@Database, @Boundary…) become a stereotype in the label («Database» Stock), because their icons are not drawn here — the name still shows, which is what matters. A name with a space goes in quotes.

Mind where this block ends up: in Mermaid, zenuml is an external diagram the page must register in JavaScript — GitHub does not do that, so the same note that draws here shows up there as a code block.

And when LightNote cannot read a diagram it says why and on which line, right below the block — a type we do not draw yet gets no warning, because nothing you wrote is wrong there.

Index

Editing

Tools

Automation (AI / scripts)

Reference