Diagrams and Math

This content was generated with the assistance of AI and should be verified against the official documentation of each tool before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

This page was written against Docusaurus 3.10.2 and checked on 2026-10-26. Docusaurus has no built-in diagrams or math, but both are one theme or two plugins away. Diagrams come from Mermaid, written as a fenced code block and drawn in the browser by @docusaurus/theme-mermaid. Math comes from KaTeX, written with dollar signs and converted to HTML while the site is built by remark-math and rehype-katex. The examples use the Pocket Units documentation site, a tiny units-conversion library written in Python and C#, in a scratch site generated by create-docusaurus@3.10.2 (classic preset, TypeScript, React 19). The site was built and served, and the pages were opened in a browser to see the result.

Two different rendering paths

The two features look alike in a document but run in different places. This matters for performance, for search engines and for what happens when the source is wrong.

A document is split into a math path rendered to HTML at build time and a Mermaid path rendered to SVG in the browser

Figure: math is turned into static HTML during the build, so a reader needs no JavaScript to see a formula. A Mermaid block is only compiled into a component during the build. The diagram itself is drawn in the browser, when the page is displayed, and is drawn again when the reader switches between light and dark mode.

Mermaid diagrams

Installation and configuration

Two things are needed, and forgetting either one gives no diagram: the theme must be listed in themes, and the Markdown compiler must be told to recognise the mermaid language with markdown.mermaid. The theme package has the same version as the other Docusaurus packages.

npm i -E @docusaurus/theme-mermaid@3.10.2

The installed theme pulled Mermaid 11.17.2 (the theme requires Mermaid 11.6.0 or later). The smallest working configuration is:

docusaurus.config.ts
import type {Config} from '@docusaurus/types';

const config: Config = {
  title: 'Pocket Units',
  url: 'https://pocket-units.example.com',
  baseUrl: '/',
  markdown: {
    mermaid: true,
  },
  themes: ['@docusaurus/theme-mermaid'],
  presets: [['classic', {docs: {routeBasePath: '/'}}]],
};

export default config;

With this in place, a fenced block whose language is mermaid becomes a diagram, in .md and .mdx files alike. The following page is the Pocket Units conversion flow:

docs/pocket-units.mdx
---
title: Pocket Units conversions
---

# Conversion flow

```mermaid
flowchart LR
    A["convert(value, 'ft', 'm')"] --> B{"Units known?"}
    B -- yes --> C["to base unit: value * factor_from"]
    C --> D["from base unit: result / factor_to"]
    B -- no --> E["raise UnknownUnitError"]
    D --> F["return result"]
```

The build succeeded and, in the browser, the block was replaced by an SVG inside an element with the class docusaurus-mermaid-container. That class is stable and meant for custom CSS. Flowchart, class and sequence diagrams from the same page rendered in the same way. Any Mermaid diagram type that the installed Mermaid version supports can be used, so the page of the diagram type in the Mermaid documentation is the reference for its syntax.

A diagram can also be passed to the component directly, which is useful when the text is built from data. The component is imported from @theme/Mermaid and receives the source in its value prop.

docs/dynamic.mdx
import Mermaid from '@theme/Mermaid';

<Mermaid value={`graph TD;
  A[Metre] --> B[Kilometre];
`} />

This component form was not executed here; only the fenced form was built.

Light and dark themes

Mermaid has its own themes, and the Docusaurus theme chooses one per colour mode. The defaults are default for the light mode and dark for the dark mode. They are changed under themeConfig.mermaid.theme. Everything under themeConfig.mermaid.options is handed to mermaid.initialize, so any Mermaid configuration key can be set there. The theme config schema accepts only these two keys, theme (with light and dark) and options.

docusaurus.config.ts (excerpt)
  themeConfig: {
    mermaid: {
      theme: {light: 'neutral', dark: 'dark'},
      options: {
        maxTextSize: 50000,
        fontFamily: 'Arial, Helvetica, sans-serif',
      },
    },
  } satisfies Preset.ThemeConfig,

The excerpt belongs inside the config object of the previous listing. When the reader toggled the colour mode in the scratch site, the diagrams were rendered again with the other theme: the fill of a node changed from a light grey to a dark grey, and nothing else on the page had to be touched. A diagram that sets its own colours with style or classDef keeps them in both modes, so colours chosen for a light page can become unreadable on a dark one. Prefer the Mermaid theme variables, or pick fills that have enough contrast on both backgrounds.

ELK layout

Mermaid lays out flowcharts with Dagre by default. ELK is an alternative engine that often produces tidier results for large graphs with many crossing edges. It is heavy (the scratch build contained about 1.4 MB of extra JavaScript chunks that mention ELK), so Docusaurus 3.9 made it an optional package. The theme detects the package when the site is built, and registers the layout in the browser if it is present. There is no Docusaurus option for it.

npm i -E @mermaid-js/layout-elk@0.1.9

The version is not arbitrary: Docusaurus 3.10.2 declares the peer range ^0.1.9 for this package. The latest release on npm at the time of writing is 1.0.1, and installing it next to the theme stops with an ERESOLVE error that names the conflicting peer. Install the 0.1.x line.

Once the package is installed, each diagram chooses its engine in a front matter block at the top of the Mermaid text. Diagrams without it keep using Dagre.

```mermaid
---
config:
  layout: elk
---
flowchart TD
    A[Value] --> B[Base unit]
    B --> C[Metre]
    B --> D[Kilometre]
    B --> E[Foot]
    B --> F[Mile]
    C --> G[Result]
    D --> G
    E --> G
    F --> G
```

The diagram rendered without errors with the package installed. The visual difference between the two engines for this small graph was not measured.

When the diagram does not render

  • The block is shown as plain code: markdown.mermaid is missing or false, or the language tag is not exactly mermaid. This was the documented behaviour and was not reproduced here.

  • The page shows a red box or an error message with the Mermaid parser output: the Mermaid text is invalid. The failure happens in the browser, so docusaurus build still succeeds. With an unfinished flowchart (A --> followed by an unclosed B[) the build passed and the page showed a message starting with Parse error on line 4, followed by the expected tokens, and a "Try again" button.

  • docusaurus build fails with a peer dependency error at install time: the ELK package is outside the ^0.1.9 range.

  • A very large diagram may exceed Mermaid’s text size limit. The maxTextSize option, set under options, raises it.

Because rendering happens in the browser, the build cannot validate diagrams. A page that is part of the Pocket Units site should be opened once after any change to its diagrams.

flowchart TD A["Diagram shows as code"] --> B{"markdown.mermaid is true?"} B -- no --> C["Set it and restart the dev server"] B -- yes --> D{"Fence language is mermaid?"} D -- no --> E["Rename the fence language"] D -- yes --> F["Look for a parse error message"]

Math with KaTeX

Installation

Math needs two plugins and one stylesheet. The official page asks for remark-math major version 6 and rehype-katex major version 7 for Docusaurus 3 (which uses MDX 3), and does not promise other versions work. The scratch site used the newest releases of those lines.

npm i -E remark-math@6.0.0 rehype-katex@7.0.1

The resolved KaTeX version was 0.16.47. remark-math teaches the Markdown parser the $ syntax. rehype-katex turns the parsed formulas into HTML and MathML. Neither produces any styling.

Configuration

Both plugins are registered on the content plugin that renders the documents (and, if formulas are used there, on the blog and pages plugins too, since each content plugin has its own remarkPlugins and rehypePlugins). The stylesheet is added to the site’s stylesheets.

docusaurus.config.ts (excerpt)
import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex';

// inside the config object
stylesheets: [
  {
    href: 'https://cdn.jsdelivr.net/npm/katex@0.16.47/dist/katex.min.css',
    type: 'text/css',
    crossorigin: 'anonymous',
  },
],
presets: [
  [
    'classic',
    {
      docs: {
        sidebarPath: './sidebars.ts',
        remarkPlugins: [remarkMath],
        rehypePlugins: [rehypeKatex],
      },
    },
  ],
],

The official page also adds an integrity hash to the stylesheet entry. It is omitted above because the hash depends on the exact file and has to be taken from the official page or computed for the version in use. To avoid a third-party CDN, copy katex.min.css and the fonts folder from a KaTeX release into static/ and point href at the local path; the official page says the .woff2 files are enough. The generated HTML contains both a MathML copy and a visual copy of each formula, and the stylesheet is what hides one of them and lays out the other, so it is required. The stylesheet in the scratch site was loaded (it appeared in the page’s loaded style sheets) and formulas rendered correctly; a build without the stylesheet was not tried.

ESM-only plugins and the config file

remark-math and rehype-katex are published only as ES modules. The docusaurus.config.ts generated by create-docusaurus is compiled as ESM, so the import lines above just work. A CommonJS docusaurus.config.js cannot do the same. With Node.js 24.21.0 a require('remark-math') was accepted, but it returns a module namespace object instead of the plugin function, and the build failed with:

Error: Expected usable value but received an empty preset, which is probably a mistake: presets typically come with
`plugins` and sometimes with `settings`, but this has neither

Older Node.js versions refuse to require an ES module at all. In a CommonJS config, export an async function and load the plugins with a dynamic import(), which is what the official page shows:

docusaurus.config.js
module.exports = async function createConfigAsync() {
  const remarkMath = (await import('remark-math')).default;
  const rehypeKatex = (await import('rehype-katex')).default;
  return {
    title: 'Pocket Units',
    url: 'https://pocket-units.example.com',
    baseUrl: '/',
    presets: [
      [
        'classic',
        {
          docs: {
            routeBasePath: '/',
            remarkPlugins: [remarkMath],
            rehypePlugins: [rehypeKatex],
          },
          blog: false,
        },
      ],
    ],
  };
};

This variant built successfully and rendered $a^2$ as a KaTeX element (the stylesheet was left out of this test).

Writing math

Inline math sits between single dollar signs. Display math goes in a fenced block with the language math, which the official page prefers; a block between $$ lines also works but relies on a syntax extension that other Markdown tools may not share.

docs/pocket-units.mdx (excerpt)
A conversion from unit $a$ to unit $b$ multiplies by the ratio of the factors: $r = v \cdot \frac{f_a}{f_b}$.

For a mile to a kilometre, $f_a = 1609.344$ and $f_b = 1000$, so $1\,\text{mi} = 1.609344\,\text{km}$.

```math
r = v \cdot \frac{f_a}{f_b}
```

$$
\sum_{i=1}^{n} \frac{1}{2^i} = 1 - \frac{1}{2^n}
$$

All four forms built. The page contained nine katex elements in the browser, and the inline, math fence and $$ forms each produced a formula. The ratio $f_a / f_b$ is the one used by the Pocket Units converter: a value is multiplied by the factor of the source unit and divided by the factor of the target unit, both relative to the metre.

Pitfalls of math

  • Dollar signs in ordinary text start a formula. In the scratch site a sentence with two prices, written with a dollar sign before each amount, was rendered as a formula that swallowed the text between the two dollar signs, with the letters run together and the spaces lost. Escaping each dollar sign with a backslash (\$5 and \$3) printed them as plain text. This affects prices and any shell variable written outside a code span.

  • A mistake in a formula does not fail the build. rehype-katex renders the error inline instead: an unbalanced formula (a \frac with its second group left open) produced an element with the class katex-error, red text, and a title attribute with the message ParseError: KaTeX parse error: Unexpected end of input in a macro argument, expected '}' at end of input. Search the built HTML for katex-error in CI to catch these.

  • Formulas are not searchable text and the dollar signs remain in the page description metadata that Docusaurus derives from the first paragraph (the generated meta description contained the raw $a^2$). Set description in the front matter for pages that begin with a formula.

  • The Mermaid and math features are independent. Installing one does not enable the other.

  • Both features are described by the Docusaurus documentation as Markdown features, so they work in .md and .mdx. The scratch site used an .mdx file; its .md variant (the CommonJS test above) also rendered math.

Pocket Units example in summary

The Pocket Units site needed these changes, all verified in the scratch site:

  • package.json: @docusaurus/theme-mermaid 3.10.2, @mermaid-js/layout-elk 0.1.9, remark-math 6.0.0 and rehype-katex 7.0.1.

  • docusaurus.config.ts: markdown.mermaid: true, themes, the stylesheets entry, the two plugins on the docs preset, and themeConfig.mermaid.

  • One page with four diagrams (a flowchart, a class diagram, a sequence diagram and an ELK flowchart) and the conversion formula.

For the other documentation tools in this section, the same two features are configured differently: see Documentation tools for the comparison. Markdown features that share the fenced block syntax, such as code block titles and tabs, are in Markdown features, and the options of the markdown key are in Configuration.

Version notes

  • Mermaid ELK layout support arrived in Docusaurus 3.9 as an optional package.

  • Docusaurus 3.10 is the last release of the 3.x line, and the 3.10 theme still pins the ELK peer range to ^0.1.9.

  • Math packages are tied to the MDX major version: remark-math@6 and rehype-katex@7 for MDX 3.