Many Diagram Languages, One Runtime Contract
Mermaid’s range of diagram forms creates an architectural problem: a flowchart, a sequence diagram, and a chart do not describe the world in the same way. This chapter examines how Mermaid JS lets those languages remain different while still entering one rendering pipeline. The practical value is a way to understand new or unfamiliar diagram types by their responsibilities—detection, parsing, semantic state, rendering, styling, and loading—rather than by memorizing a catalogue of syntaxes.
A single universal grammar would be attractive if every diagram were just a set of boxes and lines. Mermaid’s subject matter is richer than that. The architecture works because it accepts local differences at the language boundary and imposes common rules at the runtime boundary.
Different diagrams need different languages
A diagram syntax is useful when it preserves the relationships that matter to the kind of explanation being made. In a flowchart, the important structure may be branching, direction, and containment. In a sequence diagram, the order of messages and the activation of participants carries meaning. A class diagram expresses types, members, and relationships; a chart may need values, axes, series, or regions. Reducing all of these to one generic “node and edge” grammar would move the hard problem somewhere else: each renderer would have to guess which details the original author intended.
A diagram-specific grammar gives the author a vocabulary that matches the visual model. It can recognize the words, delimiters, nesting rules, and relationship forms that belong to that diagram. It can also reject input that is syntactically plausible in one language but meaningless in another. This is not only a developer convenience. An early, precise error is better than a successful render that silently discards the distinction the author was trying to express.
Mermaid therefore routes definitions through an ordered detector registry. Detection occurs after frontmatter, directives, and comments have been removed from the text used for recognition. Each detector tests whether the remaining definition belongs to its language. The first detector that returns true determines the diagram type. That type selects the parser and the rest of the diagram implementation.
The first-match rule creates a small but important dependency. Detectors do not operate in isolation from one another; their order is part of the registry’s behavior. A broad pattern placed too early can claim text that a more specific detector should handle. If a definition begins with a keyword that resembles more than one language, the registry must resolve that ambiguity deliberately. If no detector matches, Mermaid can report that it does not know which diagram type to use instead of inventing a model.
This routing step also supports an evolving project. Mermaid’s current source contains many diagram and chart families, but that list is a snapshot of a development worktree rather than a permanent promise. The important architectural fact is not the count. It is that each family can retain its own syntax while agreeing to supply the artifacts the common runtime needs.
The contract between syntax and output
The shared contract begins after detection. Mermaid’s new-diagram guidance describes a diagram as a combination of a parser, a database, a renderer, styles, and a detector; a loader can add lazy loading to the detector. Each part answers a different question.
The parser answers: “What does this text say?” It reads the diagram-specific language and turns valid constructs into semantic information. The exact parser technology can vary. The repository supports older JISON-based diagrams, Langium-based grammars, and recommends Chevrotain for new diagram work. These technologies are not interchangeable implementations, and they do not mean every diagram has the same grammar. They are alternative ways to cross the syntax boundary.
The common expectation is more important than the parser brand. Invalid input should produce a useful error with a source position rather than a raw stack trace. A parser should not leave stale information behind. It should give the diagram’s database enough semantic state for the renderer to work from the model rather than reinterpreting raw text during drawing.
A small contrast shows why that contract cannot be reduced to a generic syntax. A flowchart definition such as A --> B primarily establishes a directed relationship that a layout algorithm can place. A sequence definition such as Alice->>Bob: Hello establishes participants, message order, arrow style, and a label tied to a moment in that interaction. Both may eventually produce lines and text in SVG, but the parser must preserve different facts before the renderer can draw them. If the shared runtime accepted only anonymous nodes and edges, it would either lose those facts or force every renderer to reconstruct them from an impoverished model.
The repository’s parser history reflects that problem. Older diagram implementations use JISON, several current grammars use Langium, and the guidance for new diagram work recommends Chevrotain. These are different parser technologies and migration stages, not three user-facing modes that authors must select. Some paths produce a typed abstract syntax tree before a diagram-specific conversion; other parser arrangements update a diagram database through their parser interface. The shared contract tolerates those internal differences because it cares about the handoff that follows: a valid definition, source-aware errors, and a diagram-owned model that the renderer can query.
Source positions are part of the same design choice. A parser error that only says “invalid input” forces the author to search blindly through a definition. An error with a line and column can point to the construct whose grammar or relationship is wrong. Mermaid’s newer parsing path formats lexer and parser failures with those positions, while the preprocessing stage accounts for frontmatter and comment-preserving input when a diagram needs source-faithful locations. The result is not that every grammar behaves identically; it is that the user-facing failure boundary is more useful than an internal exception.
The database answers: “What has been understood so far?” It holds the parsed model in a form the diagram’s renderer can query. The exact contents vary: participants and messages make sense for one diagram, while entities, tasks, branches, or chart values make sense for another. Shared information such as the diagram title, accessible title, accessible description, and direction can cross the contract as well. What matters is ownership. The database belongs to the diagram definition, and its mutable state must be isolated from other renders.
The renderer answers: “How should that model become visible?” It reads the database and draws the SVG representation. The renderer is not expected to know how the parser recognized every token. That separation lets a syntax change without forcing the drawing code to become a second parser, and it lets the same host pipeline apply common output work around the renderer.
Styles answer: “How should this diagram participate in the shared visual system?” A diagram supplies a style function that consumes theme options and returns CSS. The host can then apply the active theme, rather than each diagram inventing an unrelated palette. This is why new-diagram guidance treats hardcoded colors as a defect: the extension must remain coherent when a site changes theme or moves between light, dark, or hand-drawn presentation choices.
The detector and loader answer: “When should this implementation be selected and loaded?” The detector identifies the language; the loader imports the diagram definition when that language is actually present. Registration connects the definition to Mermaid’s registry, adds its detector, makes its styles available, and can inject common utilities. An external diagram can use this same boundary without replacing the host’s core execution model.
The diagram shows a contract, not a call graph for every diagram. A detector does not draw anything. A parser does not decide the final page layout. The database is not a permanent global store. Styles do not replace the semantic model. The runtime coordinates these parts and applies shared behavior around them.
That division makes a difference when the project adds a new diagram. The extension author does not need to make the entire Mermaid host understand a new set of DOM operations. They provide the diagram-specific language and model, connect a renderer and styles, register a detector and loader, and then satisfy the project’s tests and documentation expectations. Shared code can handle registration, configuration, accessibility hooks, and common rendering utilities because those concerns cross diagram boundaries.
The contract also prevents a tempting shortcut: importing one diagram’s private implementation into another. Two diagram types may appear similar on the screen, but their state and assumptions can differ. Cross-diagram imports create hidden dependencies, so the project’s guidance requires a diagram to remain self-contained apart from explicitly shared common utilities. Local freedom is paired with a boundary around coupling.
Why state isolation and lazy loading matter
The most serious state problem is easy to describe. Imagine rendering a sequence diagram with three participants, then rendering another sequence diagram with one participant. If the database is shared and not fully cleared, the second output may retain an old participant or message. The source for the second diagram can be valid; the failure is caused by state that crossed the render boundary.
Mermaid addresses this by requiring the database to be recreated for each render or cleared completely before parsing. The preferred design for new diagrams can make a new database available each time, which makes isolation structural. A shared database can also work if its clear() operation resets every field it owns, including shared accessibility state. The rule is more durable than either implementation: mutable diagram data belongs to the database, never to module scope that survives from one render to the next.
This matters even when the page has only one visible diagram today. Public APIs can render multiple definitions, and applications can re-render an element after an edit. A design that works for one initial load can fail when an editor previews several alternatives or when a documentation page contains repeated diagram types. State isolation is therefore part of the user-visible contract, not housekeeping for maintainers.
Detector order creates a different failure surface. A wrong detector can send valid text to a parser that does not understand it. The resulting error may look like a syntax problem even though the source is valid for another diagram language. Because the first matching detector wins, registration order has to be reviewed as carefully as a grammar rule. This is one reason Mermaid keeps detection next to each diagram and makes ordering explicit in orchestration rather than hiding all recognition logic in one opaque parser.
Lazy loading introduces a performance boundary. A full diagram library can carry substantial code, including parsers, renderers, layout engines, and supporting dependencies. Loading every possible diagram before the first render would make the initial experience pay for features it does not use. A loader postpones that cost until a detector has identified the corresponding language. Mermaid also uses conditional loading for some large features and provides a smaller build that omits selected capabilities.
The trade-off is not “lazy loading is always faster.” The first use of a deferred diagram may incur an asynchronous load, and the build may fall back to a different layout when a requested large feature is unavailable. The benefit is that the runtime can scale its loaded implementation to the actual source. The extension contract must therefore describe not only what a diagram renders, but also how it is discovered and loaded.
Automatic style and utility registration complete the boundary. When a diagram is registered, its styles are added to the style system and shared helpers can be injected. This keeps extensions from duplicating the host’s sanitation, configuration access, viewBox setup, or common database behavior. It also means the extension inherits obligations that are easy to miss if it is treated as a standalone drawing: it must respect themes, expose accessibility state in the expected way, and avoid leaking mutable state.
The runtime contract is consequently a compromise between local specificity and global consistency. Each diagram language can explain a different kind of relationship, choose a parser suited to its grammar, and maintain a diagram-specific model. The host still expects a predictable handoff: detect the language, parse into isolated state, render from that state, apply shared styles and output rules, and return an artifact that the rest of Mermaid can handle.
That compromise is what keeps a growing syntax surface from becoming a collection of unrelated tools. It also gives the reader a way to reason about a new or unfamiliar diagram: ask what language it recognizes, where it stores semantic state, how it renders, how it enters the registry, and which shared guarantees it inherits. The next task is to assemble those answers into one working mental model and test where the boundaries can fail.